Files
IfcOpenShell/src/ifcfm/ifcfm/cobie24.py
T
2024-04-16 11:05:26 +02:00

1419 lines
51 KiB
Python

# IfcFM - IFC for facility management
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcFM.
#
# IfcFM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcFM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcFM. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.fm
import ifcopenshell.util.date
import ifcopenshell.util.system
import ifcopenshell.util.placement
import ifcopenshell.util.classification
# The original BIMServer plugin has a function called ifcToCOBie:
# https://github.com/opensourceBIM/COBie-plugins/blob/master/COBieShared/src/org/bimserver/cobie/shared/serialization/COBieTabSerializer.java#L54
# This calls various serialisers here:
# https://github.com/opensourceBIM/COBie-plugins/tree/master/COBieShared/src/org/bimserver/cobie/shared/serialization/util
# Some settings are also defined here:
# https://github.com/opensourceBIM/COBie-plugins/blob/master/COBiePlugins/lib/IfcToCobieConfig.xml
# Note that the following categories are not implemented in the BIMServer COBie-Plugins:
# Impact, Coordinate, Issue, Picklist
def get_contacts(ifc_file):
return ifc_file.by_type("IfcActor")
def get_facilities(ifc_file):
return ifc_file.by_type("IfcBuilding")
def get_floors(ifc_file):
return [
e
for e in ifc_file.by_type("IfcBuildingStorey")
if ifcopenshell.util.element.get_aggregate(e).is_a("IfcBuilding")
]
def get_spaces(ifc_file):
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
results = []
zones = ifc_file.by_type("IfcZone")
for zone in zones or []:
has_space = False
for rel in zone.IsGroupedBy:
items = [(zone, space) for space in rel.RelatedObjects if space.is_a("IfcSpace") and val(space.Name)]
if items:
results.extend(items)
has_space = True
if not has_space:
results.append((zone, None))
return results
def get_types(ifc_file):
return ifcopenshell.util.fm.get_cobie_types(ifc_file)
def get_components(ifc_file):
elements = set()
for element_type in get_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
results = []
components = get_components(ifc_file)
if ifc_file.schema == "IFC2X3":
systems = ifc_file.by_type("IfcSystem", include_subtypes=False)
else:
systems = ifc_file.by_type("IfcSystem", include_subtypes=False) + ifc_file.by_type("IfcDistributionSystem")
for system in systems:
for element in ifcopenshell.util.system.get_system_elements(system):
if element in components:
results.append((system, element))
return results
def get_assemblies(ifc_file):
results = []
layer_sets = ifc_file.by_type("IfcMaterialLayerSet")
layer_sets = [] # This is temporarily overridden because it is unclear exactly how this is stored in Type.
for layer_set in layer_sets:
for layer in layer_set.MaterialLayers:
results.append((None, layer_set, layer.Material))
rels = ifc_file.by_type("IfcRelAggregates") + ifc_file.by_type("IfcRelNests")
types = get_types(ifc_file)
components = get_components(ifc_file)
for rel in rels:
if rel.RelatingObject.is_a("IfcSpace"):
for related_object in rel.RelatedObjects:
if related_object.is_a("IfcSpace"):
results.append((rel, rel.RelatingObject, related_object))
elif rel.RelatingObject.is_a("IfcZone"):
for related_object in rel.RelatedObjects:
if related_object.is_a("IfcZone"):
results.append((rel, rel.RelatingObject, related_object))
elif rel.RelatingObject in types:
for related_object in rel.RelatedObjects:
if related_object in types:
results.append((rel, rel.RelatingObject, related_object))
elif rel.RelatingObject in components:
for related_object in rel.RelatedObjects:
if related_object in components:
results.append((rel, rel.RelatingObject, related_object))
elif rel.RelatingObject.is_a() in ("IfcSystem", "IfcDistributionSystem"):
for related_object in rel.RelatedObjects:
if related_object.is_a() in ("IfcSystem", "IfcDistributionSystem"):
results.append((rel, rel.RelatingObject, related_object))
return results
def get_connections(ifc_file):
return ifc_file.by_type("IfcRelConnectsPorts")
def get_spares(ifc_file):
return ifc_file.by_type("IfcConstructionProductResource")
def get_resources(ifc_file):
return ifc_file.by_type("IfcConstructionEquipmentResource")
def get_jobs(ifc_file):
return ifc_file.by_type("IfcTask")
def get_documents(ifc_file):
# The original COBie-Plugins assumes a single related object per rel. I think this was wrong.
results = []
for rel in ifc_file.by_type("IfcRelAssociatesDocument"):
doc = rel.RelatingDocument
if doc.is_a("IfcDocumentInformation"):
for related_object in rel.RelatedObjects:
results.append((rel, doc, related_object))
elif doc.is_a("IfcDocumentReference") and doc.ReferencedDocument:
for related_object in rel.RelatedObjects:
results.append((rel, doc.ReferencedDocument, related_object))
return results
def get_attributes(ifc_file):
results = []
history = get_history(ifc_file)
created_by = get_email_from_history(history) if history else None
created_on = ifcopenshell.util.date.ifc2datetime(history.CreationDate).isoformat() if history else None
external_system = history.OwningApplication.ApplicationFullName if history else None
get_sheets = {
"Facility": get_facilities,
"Floor": get_floors,
"Space": get_spaces,
"Type": get_types,
"Component": get_components,
}
# COBie-Plugins includes what seems like a whole bunch of arbitrary "at the
# time it seemed to help" exclusion names. I don't like that strategy. If
# you've got garbage in your model, clean it out first.
# fmt: off
excluded_names = {
"Manufacturer",
"ModelNumber", "ArticleNumber", "ModelLabel",
"WarrantyGuarantorParts", "PointOfContact",
"WarrantyGuarantorLabor", "PointOfContact",
"WarrantyDescription", "WarrantyIdentifier",
"ReplacementCost", "Replacement Cost", "Replacement", "Cost",
"NominalLength", "OverallLength",
"NominalWidth", "Width",
"NominalHeight", "Height",
"ModelReference", "Reference",
"Shape",
"Size",
"Color", "Colour",
"Finish",
"Grade",
"Material",
"Constituents", "Parts",
"Features",
"AccessibilityPerformance", "Access",
"CodePerformance", "Regulation",
"SustainabilityPerformance", "Environmental",
"SerialNumber", "InstallationDate", "WarrantyStartDate", "TagNumber", "BarCode", "AssetIdentifier"
}
# fmt: on
for sheet_name, get_sheet in get_sheets.items():
for element in get_sheet(ifc_file):
for pset_name, props in ifcopenshell.util.element.get_psets(element).items():
pset = ifc_file.by_id(props["id"])
pset_created_by = get_created_by(pset) or created_by
pset_created_on = get_created_on(pset) or created_on
pset_external_system = get_external_system(element) or external_system
pset_description = val(pset.Description) or pset_name
category = get_category(pset)
for name, value in props.items():
if value == "default" or not val(value):
continue
elif name in excluded_names:
continue
elif name == "id":
continue
unit = None
if isinstance(value, (int, float)):
unit = get_property_unit(props["id"], name)
allowed_values = None
if isinstance(value, (tuple, list)):
allowed_values = get_property_unit(props["id"], name)
data = {
"Name": val(name),
"CreatedBy": pset_created_by,
"CreatedOn": pset_created_on,
"Category": category,
"SheetName": sheet_name,
"RowName": val(element.Name),
"Value": value,
"Unit": unit,
"ExternalSystem": pset_external_system,
"ExternalObject": pset_name,
"ExternalIdentifier": pset.GlobalId,
"Description": pset_description,
"AllowedValues": allowed_values,
}
results.append(data)
return results
def get_contact_data(ifc_file, element):
the_actor = element.TheActor
if the_actor.is_a("IfcPerson"):
pao = None
person = the_actor
organization = None
elif the_actor.is_a("IfcOrganization"):
pao = None
person = None
organization = the_actor
elif the_actor.is_a("IfcPersonAndOrganization"):
pao = the_actor
person = the_actor.ThePerson
organization = the_actor.TheOrganization
email = get_email_from_pao(person, organization)
roles = set()
for actor in [pao, person, organization]:
if not actor:
continue
for role in actor.Roles or []:
if role.Role == "USERDEFINED":
if role.UserDefinedRole:
roles.add(role.UserDefinedRole)
else:
roles.add(role.Role)
return {
"Email": email,
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": ",".join(roles),
"Company": getattr(organization, "Name", None),
"Phone": get_pao_address(person, organization, "TelephoneNumbers"),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"Department": get_pao_address(person, organization, "InternalLocation"),
"OrganizationCode": getattr(organization, "Id", getattr(organization, "Identification", None)),
"GivenName": getattr(person, "GivenName", None),
"FamilyName": getattr(person, "FamilyName", None),
"Street": get_pao_address(person, organization, "AddressLines"),
"PostalBox": get_pao_address(person, organization, "PostalBox"),
"Town": get_pao_address(person, organization, "Town"),
"StateRegion": get_pao_address(person, organization, "Region"),
"PostalCode": get_pao_address(person, organization, "PostalCode"),
"Country": get_pao_address(person, organization, "Country"),
}
def get_facility_data(ifc_file, element):
site = get_facility_parent(element, "IfcSite")
site_name = None
site_description = None
if site:
site_name = val(site.Name)
site_description = val(site.Description)
project = None
project_name = None
project_description = None
try:
project = ifc_file.by_type("IfcProject")[0]
project_name = val(project.Name)
project_description = val(project.Description)
except:
pass
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": get_category(element),
"ProjectName": project_name,
"SiteName": site_name,
"LinearUnits": get_unit_type_name(ifc_file, "LENGTHUNIT"),
"AreaUnits": get_unit_type_name(ifc_file, "AREAUNIT"),
"VolumeUnits": get_unit_type_name(ifc_file, "VOLUMEUNIT"),
"CurrencyUnit": get_unit_type_name(ifc_file, "IfcMonetaryUnit"),
"AreaMeasurement": get_area_measurement(element),
"ExternalSystem": get_external_system(element),
"ExternalProjectObject": "IfcProject",
"ExternalProjectIdentifier": project.GlobalId if project else None,
"ExternalSiteObject": "IfcSite",
"ExternalSiteIdentifier": site.GlobalId if site else None,
"ExternalFacilityObject": "IfcBuilding",
"ExternalFacilityIdentifier": element.GlobalId,
"Description": val(element.Description),
"ProjectDescription": project_description,
"SiteDescription": site_description,
"Phase": val(project.Phase) if project else None,
}
def get_floor_data(ifc_file, element):
height_names = {
"Height",
"NetHeight",
"GrossHeight",
"Net Height",
"Gross Height",
"StoreyHeight",
"Storey Height",
"FloorHeight",
"Floor Height",
}
height = None
for _, props in ifcopenshell.util.element.get_psets(element).items():
if height is not None:
break
for name, value in props.items():
if name in height_names and val(value):
height = str(value)
break
elevation = getattr(element, "Elevation", "")
elevation = "" if elevation is None else str(elevation)
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": get_category(element),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"Description": val(element.Description),
"Elevation": val(elevation),
"Height": height,
}
def get_space_data(ifc_file, element):
floor_name = None
floor = ifcopenshell.util.element.get_aggregate(element)
if floor and floor.is_a("IfcBuildingStorey"):
floor_name = val(floor.Name)
room_tag = None
room_tag_names = {"RoomTag", "Tag", "Room Tag"}
usable_height = None
usable_height_names = {"FinishCeilingHeight", "Height", "UsableHeight"}
gross_area = None
gross_area_names = {"GrossFloorArea", "GSA"}
net_area = None
net_area_names = {"NetFloorArea", "GSA"}
for _, props in ifcopenshell.util.element.get_psets(element).items():
for name, value in props.items():
if not room_tag and name in room_tag_names and val(value):
room_tag = str(value)
if not usable_height and name in usable_height_names and val(value):
usable_height = str(value)
if not gross_area and name in gross_area_names and val(value):
gross_area = str(value)
if not net_area and name in net_area_names and val(value):
net_area = str(value)
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": get_category(element),
"FloorName": floor_name,
"Description": val(element.LongName),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
# Stopped here
"RoomTag": room_tag,
"UsableHeight": usable_height,
"GrossArea": gross_area,
"NetArea": net_area,
}
def get_zone_data(ifc_file, element):
zone, space = element
name = zone.Name
parent = ifcopenshell.util.element.get_aggregate(zone)
if parent and val(parent.Name):
name = parent.Name + "-" + name
category = get_category(zone)
space_name = val(space.Name) if space else None
return {
"Name": name,
"CreatedBy": get_created_by(zone),
"CreatedOn": get_created_on(zone),
"Category": category,
"SpaceNames": space_name,
"ExternalSystem": get_external_system(zone),
"ExternalObject": zone.is_a(),
"ExternalIdentifier": zone.GlobalId,
"Description": val(zone.Description) or zone.Name,
}
def get_type_data(ifc_file, element):
asset_type = None
manufacturer = None
model_number = None
warranty_guarantor_parts = None
warranty_guarantor_labor = None
warranty_duration_parts = None
warranty_duration_labor = None
warranty_duration_unit = None
replacement_cost = None
expected_life = None
duration_unit = None
warranty_description = None
nominal_length = None
nominal_width = None
nominal_height = None
model_reference = None
shape = None
size = None
color = None
finish = None
grade = None
material = None
constituents = None
features = None
accessibility_performance = None
code_performance = None
sustainability_performance = None
for pset_name, props in ifcopenshell.util.element.get_psets(element).items():
pset_warranty_type = None
if pset_name == "COBie_Warranty":
warranty_guarantor_parts = props.get("WarrantyGuarantorParts", None)
warranty_guarantor_labor = props.get("WarrantyGuarantorLabor", None)
warranty_duration_parts = props.get("WarrantyDurationParts", None)
warranty_duration_labor = props.get("WarrantyDurationLabor", None)
warranty_duration_unit = props.get("WarrantyDurationUnit", None)
warranty_description = props.get("WarrantyDescription", None)
elif pset_name == "COBie_Asset":
asset_type = props.get("AssetType", None)
elif pset_name == "COBie_EconomicImpactValues":
replacement_cost = props.get("ReplacementCost", None)
elif pset_name == "COBie_ServiceLife":
duration_unit = props.get("DurationUnit", None)
duration_unit = props.get("ServiceLifeDuration", props.get("ExpectedLife", None))
elif pset_name == "COBie_Specification":
nominal_length = props.get("NominalLength", None)
nominal_width = props.get("NominalWidth", None)
nominal_height = props.get("NominalHeight", None)
shape = props.get("Shape", None)
size = props.get("Size", None)
color = props.get("Color", None)
finish = props.get("Finish", None)
grade = props.get("Grade", None)
material = props.get("Material", None)
constituents = props.get("Constituents", None)
features = props.get("Features", None)
accessibility_performance = props.get("AccessibilityPerformance", None)
code_performance = props.get("CodePerformance", None)
sustainability_performance = props.get("SustainabilityPerformance", None)
elif pset_name == "Pset_ServiceLife":
duration_unit = props.get("ServiceLifeDuration", None)
elif pset_name == "Pset_ManufacturerTypeInformation":
manufacturer = props.get("Manufacturer", None)
model_number = props.get("ModelLabel", None)
model_reference = props.get("ModelReference", None)
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": get_category(element),
"Description": val(element.Description),
"AssetType": asset_type,
"Manufacturer": manufacturer,
"ModelNumber": model_number,
"WarrantyGuarantorParts": warranty_guarantor_parts,
"WarrantyDurationParts": warranty_duration_parts,
"WarrantyGuarantorLabor": warranty_guarantor_labor,
"WarrantyDurationLabor": warranty_duration_labor,
"WarrantyDurationUnit": warranty_duration_unit,
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"ReplacementCost": replacement_cost,
"ExpectedLife": expected_life,
"DurationUnit": duration_unit,
"WarrantyDescription": warranty_description,
"NominalLength": nominal_length,
"NominalWidth": nominal_width,
"NominalHeight": nominal_height,
"ModelReference": model_reference,
"Shape": shape,
"Size": size,
"Color": color,
"Finish": finish,
"Grade": grade,
"Material": material,
"Constituents": constituents,
"Features": features,
"AccessibilityPerformance": accessibility_performance,
"CodePerformance": code_performance,
"SustainabilityPerformance": sustainability_performance,
}
def get_component_data(ifc_file, element):
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0].Name if systems else None
type_name = None
relating_type = ifcopenshell.util.element.get_type(element)
if relating_type and val(relating_type.Name):
type_name = relating_type.Name
else:
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
type_name = getattr(material, "Name", None) or getattr(material, "LayerSetName", None)
serial_number = None
installation_date = None
warranty_start_date = None
tag_number = None
bar_code = None
asset_identifier = None
for _, props in ifcopenshell.util.element.get_psets(element).items():
for name, value in props.items():
if not serial_number and name == "SerialNumber" and val(value):
serial_number = str(value)
if not installation_date and name == "InstallationDate" and val(value):
installation_date = str(value)
if not warranty_start_date and name == "WarrantyStartDate" and val(value):
warranty_start_date = str(value)
if not tag_number and name == "TagNumber" and val(value):
tag_number = str(value)
if not bar_code and name == "BarCode" and val(value):
bar_code = str(value)
if not asset_identifier and name == "AssetIdentifier" and val(value):
asset_identifier = str(value)
return {
"Name": element.Name,
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"TypeName": type_name,
"Space": space_name,
"Description": val(element.Description) or val(element.Name),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"SerialNumber": serial_number,
"InstallationDate": installation_date,
"WarrantyStartDate": warranty_start_date,
"TagNumber": tag_number,
"BarCode": bar_code,
"AssetIdentifier": asset_identifier,
}
def get_system_data(ifc_file, element):
system, component = element
category = get_category(system)
component_name = val(component.Name)
return {
"Name": val(system.Name),
"CreatedBy": get_created_by(system),
"CreatedOn": get_created_on(system),
"Category": get_category(system),
"ComponentNames": component_name,
"ExternalSystem": get_external_system(system),
"ExternalObject": system.is_a(),
"ExternalIdentifier": system.GlobalId,
"Description": val(system.Description) or val(system.Name),
}
def get_assembly_data(ifc_file, element):
rel, relating_object, related_object = element
if relating_object.is_a("IfcMaterialLayerSet"):
name = val(relating_object.LayerSetName)
parent_name = name
if name:
name += " assembly"
assembly_type = "Layer"
sheet_name = "Type"
description = val(relating_object.LayerSetName)
else:
name = val(relating_object.Name)
parent_name = name
assembly_type = "Fixed"
sheet_name = get_sheet_name(relating_object)
description = val(rel.Description) or val(rel.Name)
child_name = val(related_object.Name)
history = get_history(ifc_file)
return {
"Name": name,
"CreatedBy": get_email_from_history(history) if history else None,
"CreatedOn": ifcopenshell.util.date.ifc2datetime(history.CreationDate).isoformat() if history else None,
"SheetName": sheet_name,
"ParentName": parent_name,
"ChildNames": child_name,
"AssemblyType": assembly_type,
"ExternalSystem": history.OwningApplication.ApplicationFullName if history else None,
"ExternalObject": rel.is_a() if rel else relating_object.is_a(),
"ExternalIdentifier": rel.GlobalId if rel else None,
"Description": description,
}
def get_connection_data(ifc_file, element):
connection_type = (
val(element.RelatingPort.ObjectType)
or val(element.RelatedPort.ObjectType)
or val(element.Description)
or val(element.Name)
)
name = val(element.Name)
row_name1 = val(ifcopenshell.util.system.get_port_element(element.RelatingPort).Name)
row_name2 = val(ifcopenshell.util.system.get_port_element(element.RelatedPort).Name)
return {
"Name": name,
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"ConnectionType": connection_type,
"SheetName": "Component",
"RowName1": row_name1,
"RowName2": row_name2,
"RealizingElement": val(element.RealizingElement.Name) if element.RealizingElement else None,
"PortName1": val(element.RelatingPort.Name),
"PortName2": val(element.RelatedPort.Name),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"Description": val(element.Description) or val(element.Name),
}
def get_spare_data(ifc_file, element):
type_name = None
for rel in element.ResourceOf or []:
for related_object in rel.RelatedObjects or []:
if val(related_object.Name):
type_name = val(related_object.Name)
suppliers = None
set_number = None
part_number = None
for _, props in ifcopenshell.util.element.get_psets(element).items():
for name, value in props.items():
if name == "Suppliers" and val(value):
suppliers = str(value)
if name == "SetNumber" and val(value):
set_number = str(value)
if name == "PartNumber" and val(value):
part_number = str(value)
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": get_category(element),
"TypeName": type_name,
"Suppliers": suppliers,
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"Description": val(element.Description) or val(element.Name),
"SetNumber": set_number,
"PartNumber": part_number,
}
def get_resource_data(ifc_file, element):
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": val(element.ObjectType),
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"Description": val(element.Description) or val(element.Name),
}
def get_job_data(ifc_file, element):
type_names = []
resource_names = []
for rel in element.OperatesOn or []:
for related_object in rel.RelatedObjects or []:
if not val(related_object.Name):
continue
if related_object.is_a("IfcTypeObject"):
type_names.append(related_object.Name)
elif related_object.is_a("IfcConstructionEquipmentResource"):
resource_names.append(related_object.Name)
type_name = ",".join(type_names) if type_names else None
resource_names = ",".join(resource_names) if resource_names else None
duration = None
duration_unit = None
start = None
task_start_unit = None
frequency = None
frequency_unit = None
for _, props in ifcopenshell.util.element.get_psets(element).items():
pset = ifc_file.by_id(props["id"])
for name, value in props.items():
if not duration and name == "TaskDuration" and val(value):
duration = str(value)
if not duration_unit:
duration_unit = get_property_unit(pset, name)
if not start and name == "TaskStartDate" and val(value):
start = str(value)
if not task_start_unit:
task_start_unit = get_property_unit(pset, name)
if not frequency and name == "TaskInterval" and val(value):
frequency = str(value)
if not frequency_unit:
frequency_unit = get_property_unit(pset, name)
task_number = val(getattr(element, "Id", None)) or val(getattr(element, "Identification", None))
priors = []
for rel in element.IsSuccessorFrom or []:
if rel.RelatedProcess.is_a("IfcTask"):
prior_task = rel.RelatedProcess
prior_id = val(getattr(prior_task, "Id", None)) or val(getattr(prior_task, "Identification", None))
if prior_id:
priors.append(prior_id)
priors = ",".join(priors) if priors else task_number
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
"CreatedOn": get_created_on(element),
"Category": val(element.ObjectType),
"Status": val(element.Status),
"TypeName": type_name,
"Description": val(element.Description) or val(element.Name),
"Duration": duration,
"DurationUnit": duration_unit,
"Start": start,
"TaskStartUnit": task_start_unit,
"Frequency": frequency,
"FrequencyUnit": frequency_unit,
"ExternalSystem": get_external_system(element),
"ExternalObject": element.is_a(),
"ExternalIdentifier": element.GlobalId,
"TaskNumber": task_number,
"Priors": priors,
"ResourceNames": resource_names,
}
def get_document_data(ifc_file, element):
rel, doc, related_object = element
directory = getattr(doc, "Location", None)
file = None
if not directory:
references = getattr(doc, "DocumentReferences", []) or getattr(doc, "HasDocumentReferences", [])
for reference in references or []:
if val(reference.Location):
directory = reference.Location
identification = getattr(reference, "ItemReference", None) or getattr(reference, "Identification", None)
if val(reference.Name) and doc.Name != reference.Name:
file = reference.Name
elif val(identification):
file = identification
name = val(doc.Name)
stage = val(doc.Scope) or "Requirement"
sheet_name = get_sheet_name(related_object)
row_name = val(related_object.Name)
return {
"Name": name,
"CreatedBy": get_created_by(rel),
"CreatedOn": get_created_on(rel),
"Category": val(doc.Purpose),
"ApprovalBy": val(doc.IntendedUse) or "Information Only",
"Stage": stage,
"SheetName": sheet_name,
"RowName": row_name,
"Directory": directory,
"File": file,
"ExternalSystem": get_external_system(rel),
"ExternalObject": rel.is_a(),
"ExternalIdentifier": rel.GlobalId,
"Description": val(doc.Description),
"Reference": val(doc.Description) or val(doc.Name),
}
def get_attribute_data(ifc_file, element):
return element
def get_unit_type_name(ifc_file, unit_type):
for unit in ifc_file.by_type("IfcUnitAssignment")[0].Units:
if unit.is_a("IfcNamedUnit") and unit.UnitType == unit_type:
if unit.is_a("IfcSIUnit"):
prefix = (unit.Prefix or "").lower()
if unit_type == "LENGTHUNIT":
return f"{prefix}meters"
elif unit_type == "AREAUNIT":
return f"square {prefix}meters"
elif unit_type == "VOLUMEUNIT":
return f"cubic {prefix}meters"
else:
return val(unit.Name)
elif unit.is_a("IfcMonetaryUnit") and unit_type == "IfcMonetaryUnit":
return val(unit.Currency)
def get_unit_name(ifc_file, unit):
if unit.is_a("IfcNamedUnit"):
return val(unit.Name)
def get_created_by(element):
if getattr(element, "OwnerHistory", None):
return get_email_from_history(element.OwnerHistory)
def get_email_from_history(element):
pao = element.OwningUser
if pao.is_a("IfcPersonAndOrganization"):
return get_email_from_pao(pao.ThePerson, pao.TheOrganization)
elif pao.is_a("IfcPerson"):
return get_email_from_pao(pao, None)
elif pao.is_a("IfcOrganization"):
return get_email_from_pao(None, pao)
def get_email_from_pao(person, organization):
if organization:
for address in organization.Addresses or []:
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
return address.ElectronicMailAddresses[0]
if person:
for address in person.Addresses or []:
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
return address.ElectronicMailAddresses[0]
def get_owner_name(element):
if getattr(element, "OwnerHistory", None):
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_created_on(element):
if getattr(element, "OwnerHistory", None):
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
return "1900-12-31T23:59:59" # Yes, really
def get_external_system(element):
if getattr(element, "OwnerHistory", None):
return val(element.OwnerHistory.OwningApplication.ApplicationFullName)
def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
return parent
if parent.is_a("IfcProject"):
return
parent = ifcopenshell.util.element.get_aggregate(parent)
def val(x):
return x if x not in ("", "n/a") else None
def get_area_measurement(element):
for relationship in getattr(element, "IsDefinedBy", []) or []:
if relationship.is_a("IfcRelDefinesByProperties"):
definition = relationship.RelatingPropertyDefinition
if definition.is_a("IfcElementQuantity") and val(definition.MethodOfMeasurement):
return definition.MethodOfMeasurement
for rel in getattr(element, "IsDecomposedBy", []):
for related_object in rel.RelatedObjects:
result = get_area_measurement(related_object)
if result:
return result
psets = ifcopenshell.util.element.get_psets(element)
if psets:
for _, props in psets.items():
for name, value in props.items():
if name == "MethodOfMeasurement":
return value
def get_category(element):
references = list(ifcopenshell.util.classification.get_references(element))
results = []
for reference in references:
if reference.is_a("IfcClassification"):
continue
elif reference.is_a("IfcClassificationReference"):
identification = val(getattr(reference, "Identification", getattr(reference, "ItemReference", None)))
if val(reference.Name) and identification and val(reference.Name) != identification:
results.append(identification + " : " + val(reference.Name))
elif val(reference.Name):
results.append(reference.Name)
elif identification:
results.append(identification)
if results:
return ",".join(results)
def get_pao_address(person, organization, name):
for actor in [organization, person]:
if not actor:
continue
for address in actor.Addresses or []:
if hasattr(address, name) and getattr(address, name, None):
result = getattr(address, name)
if isinstance(result, tuple):
if name == "AddressLines":
return " ".join(result)
return result[0]
return result
def get_property(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_history(ifc_file):
histories = ifc_file.by_type("IfcOwnerHistory")
if histories:
return sorted(histories, key=lambda x: x.id())[-1]
def get_property_unit(pset, prop_name):
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
unit = getattr(prop, "Unit", None)
if unit:
return get_unit_name(unit)
def get_allowed_values(pset_id, prop_name):
pset = ifc_file.by_id(pset_id)
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
if prop.is_a("IfcPropertyEnumeratedValue") and prop.EnumerationValues:
return ",".join([v.wrappedValue for v in prop.EnumerationValues])
def get_sheet_name(element):
if element.is_a("IfcBuilding"):
return "Facility"
elif element.is_a("IfcBuildingStorey"):
return "Floor"
elif element.is_a("IfcSpace"):
return "Space"
elif element.is_a("IfcZone"):
return "Zone"
elif element.is_a("IfcSystem"):
return "System"
elif element.is_a("IfcElementType"):
return "Type"
elif element.is_a("IfcElement"):
return "Component"
elif element.is_a("IfcTask"):
return "Job"
elif element.is_a("IfcConstructionProductResource"):
return "Spare"
elif element.is_a("IfcConstructionEquipmentResource"):
return "Resource"
config = {
"colours": {
"h": "c0c0c0", # Header data
"r": "ffff99", # Required
"i": "ffcc99", # Internal reference
"e": "cc99ff", # External reference
"o": "ccffcc", # Optionally specified
"s": "c0c0c0", # Secondary product data
"b": "99ccff", # Bespoke data
"x": "000000", # Not in scope
},
"null": "n/a",
"empty": "n/a",
"bool_true": "Yes",
"bool_false": "No",
"categories": {
"Contact": {
"keys": ["Email"],
"headers": [
"Email",
"CreatedBy",
"CreatedOn",
"Category",
"Company",
"Phone",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Department",
"OrganizationCode",
"GivenName",
"FamilyName",
"Street",
"PostalBox",
"Town",
"StateRegion",
"PostalCode",
"Country",
],
"colours": "rrrrrreeeoooooooooo",
"sort": [{"name": "Email", "order": "ASC"}],
"get_category_elements": get_contacts,
"get_element_data": get_contact_data,
},
"Facility": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"ProjectName",
"SiteName",
"LinearUnits",
"AreaUnits",
"VolumeUnits",
"CurrencyUnit",
"AreaMeasurement",
"ExternalSystem",
"ExternalProjectObject",
"ExternalProjectIdentifier",
"ExternalSiteObject",
"ExternalSiteIdentifier",
"ExternalFacilityObject",
"ExternalFacilityIdentifier",
"Description",
"ProjectDescription",
"SiteDescription",
"Phase",
],
"colours": "ririrriiiireeeeeeeoooo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_facilities,
"get_element_data": get_facility_data,
},
"Floor": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
"Elevation",
"Height",
],
"colours": "ririeeeooo",
"sort": [{"name": "Elevation", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
"get_category_elements": get_floors,
"get_element_data": get_floor_data,
},
"Space": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"FloorName",
"Description",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"RoomTag",
"UsableHeight",
"GrossArea",
"NetArea",
],
"colours": "ririrreeeoooo",
"sort": [{"name": "FloorName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
"get_category_elements": get_spaces,
"get_element_data": get_space_data,
},
"Zone": {
"keys": ["Name", "Category", "SpaceNames"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"SpaceNames",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
],
"colours": "ririreeeo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_zones,
"get_element_data": get_zone_data,
},
"Type": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"Description",
"AssetType",
"Manufacturer",
"ModelNumber",
"WarrantyGuarantorParts",
"WarrantyDurationParts",
"WarrantyGuarantorLabor",
"WarrantyDurationLabor",
"WarrantyDurationUnit",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"ReplacementCost",
"ExpectedLife",
"DurationUnit",
"WarrantyDescription",
"NominalLength",
"NominalWidth",
"NominalHeight",
"ModelReference",
"Shape",
"Size",
"Color",
"Finish",
"Grade",
"Material",
"Constituents",
"Features",
"AccessibilityPerformance",
"CodePerformance",
"SustainabilityPerformance",
],
"colours": "riririiriririeeeooiorrroooooooooooo",
"sort": [{"name": "ExternalObject", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
"get_category_elements": get_types,
"get_element_data": get_type_data,
},
"Component": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"TypeName",
"Space",
"Description",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"SerialNumber",
"InstallationDate",
"WarrantyStartDate",
"TagNumber",
"BarCode",
"AssetIdentifier",
],
"colours": "ririireeeoooooo",
"sort": [
{"name": "ExternalObject", "order": "ASC"},
{"name": "TypeName", "order": "ASC"},
{"name": "Name", "order": "ASC"},
],
"get_category_elements": get_components,
"get_element_data": get_component_data,
},
"System": {
"keys": ["Name", "Category", "ComponentNames"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"ComponentNames",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
],
"colours": "ririieeeo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_systems,
"get_element_data": get_system_data,
},
"Assembly": { # Note that this is technically "not required"
"keys": ["Name", "SheetName", "ParentName"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"SheetName",
"ParentName",
"ChildNames",
"AssemblyType",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
],
"colours": "ririiiieeeo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_assemblies,
"get_element_data": get_assembly_data,
},
"Connection": { # Note that this is technically "not required"
"keys": ["Name", "ConnectionType", "RowName1", "RowName2"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"ConnectionType",
"SheetName",
"RowName1",
"RowName2",
"RealizingElement",
"PortName1",
"PortName2",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
],
"colours": "ririiiiiiieeeo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_connections,
"get_element_data": get_connection_data,
},
"Spare": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"TypeName",
"Suppliers",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
"SetNumber",
"PartNumber",
],
"colours": "ririiieeeooo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_spares,
"get_element_data": get_spare_data,
},
"Resource": {
"keys": ["Name"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
],
"colours": "ririeeeo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_resources,
"get_element_data": get_resource_data,
},
"Job": {
"keys": ["Name", "TypeName", "TaskNumber"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"Status",
"TypeName",
"Description",
"Duration",
"DurationUnit",
"Start",
"TaskStartUnit",
"Frequency",
"FrequencyUnit",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"TaskNumber",
"Priors",
"ResourceNames",
],
"colours": "ririiirriririeeeoii",
"sort": [{"name": "TypeName", "order": "ASC"}, {"name": "TaskNumber", "order": "ASC"}],
"get_category_elements": get_jobs,
"get_element_data": get_job_data,
},
"Document": {
"keys": ["Name", "Stage", "SheetName", "RowName"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"ApprovalBy",
"Stage",
"SheetName",
"RowName",
"Directory",
"File",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
"Reference",
],
"colours": "ririiiiirreeeoo",
"sort": [{"name": "Name", "order": "ASC"}],
"get_category_elements": get_documents,
"get_element_data": get_document_data,
},
"Attribute": {
"keys": ["Name", "SheetName", "RowName"],
"headers": [
"Name",
"CreatedBy",
"CreatedOn",
"Category",
"SheetName",
"RowName",
"Value",
"Unit",
"ExternalSystem",
"ExternalObject",
"ExternalIdentifier",
"Description",
"AllowedValues",
],
"colours": "ririiirreeeoo",
"sort": [{"name": "Category", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
"get_category_elements": get_attributes,
"get_element_data": get_attribute_data,
},
},
}