mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 02:31:09 +00:00
Reimplement IfcCOBie contacts, facilities, floors, and spaces in IfcFM.
This commit is contained in:
@@ -1,494 +0,0 @@
|
|||||||
# 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 os
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
import datetime
|
|
||||||
import ifcopenshell
|
|
||||||
import ifcopenshell.util.fm
|
|
||||||
import ifcopenshell.util.date
|
|
||||||
import ifcopenshell.util.schema
|
|
||||||
import ifcopenshell.util.system
|
|
||||||
import ifcopenshell.util.placement
|
|
||||||
import ifcopenshell.util.classification
|
|
||||||
|
|
||||||
try:
|
|
||||||
from openpyxl import Workbook
|
|
||||||
from openpyxl.styles import PatternFill
|
|
||||||
except:
|
|
||||||
pass # No XLSX support
|
|
||||||
|
|
||||||
try:
|
|
||||||
from odf.opendocument import OpenDocumentSpreadsheet
|
|
||||||
from odf.style import Style, TableCellProperties
|
|
||||||
from odf.table import Table, TableRow, TableCell
|
|
||||||
from odf.text import P
|
|
||||||
except:
|
|
||||||
pass # No ODF support
|
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
|
||||||
def __init__(self, preset="BASIC"):
|
|
||||||
self.file = None
|
|
||||||
self.categories = {}
|
|
||||||
self.get_category_elements = {}
|
|
||||||
self.get_element_data = {}
|
|
||||||
self.get_custom_element_data = {}
|
|
||||||
self.duplicate_keys = []
|
|
||||||
|
|
||||||
if preset == "BASIC":
|
|
||||||
self.get_category_elements = {
|
|
||||||
"Actors": get_actors,
|
|
||||||
"Facilities": get_facilities,
|
|
||||||
"Storeys": get_storeys,
|
|
||||||
"Spaces": get_spaces,
|
|
||||||
"Zones": get_zones,
|
|
||||||
"Types": get_types,
|
|
||||||
"Elements": get_elements,
|
|
||||||
"Systems": get_systems,
|
|
||||||
}
|
|
||||||
self.get_element_data = {
|
|
||||||
"Actors": get_actor_data,
|
|
||||||
"Facilities": get_facility_data,
|
|
||||||
"Storeys": get_storey_data,
|
|
||||||
"Spaces": get_space_data,
|
|
||||||
"Zones": get_zone_data,
|
|
||||||
"Types": get_type_data,
|
|
||||||
"Elements": get_element_data,
|
|
||||||
"Systems": get_system_data,
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(self, ifc_file):
|
|
||||||
for category_name, get_category_elements in self.get_category_elements.items():
|
|
||||||
self.categories.setdefault(category_name, {})
|
|
||||||
for element in get_category_elements(ifc_file):
|
|
||||||
get_element_data = self.get_element_data[category_name]
|
|
||||||
|
|
||||||
if isinstance(get_element_data, dict):
|
|
||||||
data = {}
|
|
||||||
for key, query in get_element_data.items():
|
|
||||||
data[key] = ifcopenshell.util.selector.get_element_value(element, query)
|
|
||||||
else:
|
|
||||||
data = get_element_data(ifc_file, element) or {}
|
|
||||||
|
|
||||||
get_custom_element_data = self.get_custom_element_data.get(category_name, lambda x, y: None)
|
|
||||||
if isinstance(get_element_data, dict):
|
|
||||||
custom_data = {}
|
|
||||||
for key, query in get_custom_element_data.items():
|
|
||||||
custom_data[key] = ifcopenshell.util.selector.get_element_value(element, query)
|
|
||||||
else:
|
|
||||||
custom_data = get_custom_element_data(ifc_file, element) or {}
|
|
||||||
|
|
||||||
data.update(custom_data)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
key = data["key"]
|
|
||||||
del data["key"]
|
|
||||||
if key in self.categories[category_name]:
|
|
||||||
self.duplicate_keys.append((self.categories[category_name][key], data))
|
|
||||||
self.categories[category_name][key] = data
|
|
||||||
|
|
||||||
|
|
||||||
class Writer:
|
|
||||||
def __init__(self, parser, colours=None, preset="BASIC"):
|
|
||||||
self.parser = parser
|
|
||||||
if colours:
|
|
||||||
self.colours = colours
|
|
||||||
else:
|
|
||||||
self.colours = {
|
|
||||||
"h": "dddddd", # Header data
|
|
||||||
"p": "dc8774", # Primary identification data
|
|
||||||
"s": "b8dd73", # Secondary asset data
|
|
||||||
"r": "eda786", # Internal reference
|
|
||||||
"e": "96c7d0", # External / autogenerated data
|
|
||||||
"o": "ddb873", # Conditional / optional data
|
|
||||||
"n": "eeeeee", # Other data
|
|
||||||
"b": "000000", # Not in scope
|
|
||||||
}
|
|
||||||
if preset == "BASIC":
|
|
||||||
self.config = {
|
|
||||||
"Actors": {"colours": "ppssssssss", "sort": [{"name": "Name", "order": "ASC"}]},
|
|
||||||
"Facilities": {"colours": "ppppreeeeessss", "sort": [{"name": "Name", "order": "ASC"}]},
|
|
||||||
"Storeys": {
|
|
||||||
"colours": "ppreeees",
|
|
||||||
"sort": [{"name": "Elevation", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
|
||||||
},
|
|
||||||
"Spaces": {
|
|
||||||
"colours": "ppprreeess",
|
|
||||||
"sort": [{"name": "LevelName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
|
||||||
},
|
|
||||||
"Zones": {"colours": "prreee", "sort": [{"name": "Name", "order": "ASC"}]},
|
|
||||||
"Types": {
|
|
||||||
"colours": "pppreeeee",
|
|
||||||
"sort": [{"name": "ModelObject", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
|
||||||
},
|
|
||||||
"Elements": {
|
|
||||||
"colours": "prrrreeee",
|
|
||||||
"sort": [{"name": "TypeName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
|
||||||
},
|
|
||||||
"Systems": {"colours": "pppreee", "sort": [{"name": "Name", "order": "ASC"}]},
|
|
||||||
}
|
|
||||||
|
|
||||||
def write(self, null="N/A", empty="-", bool_true="YES", bool_false="NO"):
|
|
||||||
self.categories = {}
|
|
||||||
for category, data in self.parser.categories.items():
|
|
||||||
if not data:
|
|
||||||
self.categories[category] = {"headers": [], "rows": []}
|
|
||||||
continue
|
|
||||||
|
|
||||||
headers = list(data[list(data.keys())[0]].keys())
|
|
||||||
rows = []
|
|
||||||
for row in data.values():
|
|
||||||
processed_row = []
|
|
||||||
for value in row.values():
|
|
||||||
if value is None:
|
|
||||||
value = null
|
|
||||||
elif value == "":
|
|
||||||
value = empty
|
|
||||||
elif value is True:
|
|
||||||
value = bool_true
|
|
||||||
elif value is False:
|
|
||||||
value = bool_false
|
|
||||||
processed_row.append(value)
|
|
||||||
rows.append(processed_row)
|
|
||||||
|
|
||||||
sort = self.config.get(category, {}).get("sort", None)
|
|
||||||
if sort:
|
|
||||||
|
|
||||||
def natural_sort(value):
|
|
||||||
if isinstance(value, str):
|
|
||||||
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
|
||||||
return [convert(c) for c in re.split("([0-9]+)", value)]
|
|
||||||
return value
|
|
||||||
|
|
||||||
# Sort least important keys first, then more important keys.
|
|
||||||
# https://stackoverflow.com/questions/11476371/sort-by-multiple-keys-using-different-orderings
|
|
||||||
for sort_data in reversed(sort):
|
|
||||||
i = headers.index(sort_data["name"])
|
|
||||||
reverse = sort_data["order"] == "DESC"
|
|
||||||
rows = sorted(rows, key=lambda x: natural_sort(x[i]), reverse=reverse)
|
|
||||||
self.categories[category] = {"headers": headers, "rows": rows}
|
|
||||||
|
|
||||||
def write_csv(self, output, delimiter=","):
|
|
||||||
filename = None
|
|
||||||
if len(self.categories.keys()) == 1 and "." in os.path.basename(output):
|
|
||||||
filename = output
|
|
||||||
for category, data in self.categories.items():
|
|
||||||
category_filename = filename or os.path.join(output, f"{category}.csv")
|
|
||||||
with open(category_filename, "w", newline="", encoding="utf-8") as f:
|
|
||||||
writer = csv.writer(f, delimiter=delimiter)
|
|
||||||
writer.writerow(data["headers"])
|
|
||||||
for row in data["rows"]:
|
|
||||||
writer.writerow(row)
|
|
||||||
|
|
||||||
def write_ods(self, output):
|
|
||||||
doc = OpenDocumentSpreadsheet()
|
|
||||||
|
|
||||||
for key, value in self.colours.items():
|
|
||||||
style = Style(name=key, family="table-cell")
|
|
||||||
style.addElement(TableCellProperties(backgroundcolor="#" + value))
|
|
||||||
doc.automaticstyles.addElement(style)
|
|
||||||
|
|
||||||
for category, data in self.categories.items():
|
|
||||||
colours = self.config.get(category, {}).get("colours", [])
|
|
||||||
|
|
||||||
table = Table(name=category)
|
|
||||||
tr = TableRow()
|
|
||||||
for header in data["headers"]:
|
|
||||||
tc = TableCell(valuetype="string", stylename="h")
|
|
||||||
tc.addElement(P(text=header))
|
|
||||||
tr.addElement(tc)
|
|
||||||
table.addElement(tr)
|
|
||||||
for row in data["rows"]:
|
|
||||||
tr = TableRow()
|
|
||||||
c = 0
|
|
||||||
for col in row:
|
|
||||||
if c >= len(colours):
|
|
||||||
cell_format = "n"
|
|
||||||
else:
|
|
||||||
cell_format = colours[c]
|
|
||||||
tc = TableCell(valuetype="string", stylename=cell_format)
|
|
||||||
tc.addElement(P(text=str(col)))
|
|
||||||
tr.addElement(tc)
|
|
||||||
c += 1
|
|
||||||
table.addElement(tr)
|
|
||||||
doc.spreadsheet.addElement(table)
|
|
||||||
|
|
||||||
if len(output) > 4 and output[-4:].lower() == ".ods":
|
|
||||||
output = output[0:-4]
|
|
||||||
|
|
||||||
doc.save(output, True)
|
|
||||||
|
|
||||||
def write_xlsx(self, output):
|
|
||||||
workbook = Workbook()
|
|
||||||
|
|
||||||
cell_formats = {}
|
|
||||||
for key, value in self.colours.items():
|
|
||||||
fill = PatternFill(start_color=value, end_color=value, fill_type="solid")
|
|
||||||
cell_formats[key] = fill
|
|
||||||
|
|
||||||
for category, data in self.categories.items():
|
|
||||||
colours = self.config.get(category, {}).get("colours", [])
|
|
||||||
|
|
||||||
if category in workbook.sheetnames:
|
|
||||||
worksheet = workbook[category]
|
|
||||||
else:
|
|
||||||
worksheet = workbook.create_sheet(category)
|
|
||||||
|
|
||||||
r = 1 # Openpyxl uses 1-based indexing
|
|
||||||
c = 1
|
|
||||||
for header in data["headers"]:
|
|
||||||
cell = worksheet.cell(row=r, column=c, value=header)
|
|
||||||
cell.fill = cell_formats["h"]
|
|
||||||
c += 1
|
|
||||||
|
|
||||||
r += 1
|
|
||||||
for row in data["rows"]:
|
|
||||||
c = 1
|
|
||||||
for col in row:
|
|
||||||
if c > len(colours): # Adjusted the comparison
|
|
||||||
cell_format = "n"
|
|
||||||
else:
|
|
||||||
cell_format = colours[c - 1] # Adjusted the indexing
|
|
||||||
cell = worksheet.cell(row=r, column=c, value=col)
|
|
||||||
cell.fill = cell_formats[cell_format]
|
|
||||||
c += 1
|
|
||||||
r += 1
|
|
||||||
|
|
||||||
workbook.save(output)
|
|
||||||
|
|
||||||
|
|
||||||
def get_actors(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcActor")
|
|
||||||
|
|
||||||
|
|
||||||
def get_facilities(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcBuilding")
|
|
||||||
|
|
||||||
|
|
||||||
def get_storeys(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcBuildingStorey")
|
|
||||||
|
|
||||||
|
|
||||||
def get_spaces(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcSpace")
|
|
||||||
|
|
||||||
|
|
||||||
def get_zones(ifc_file):
|
|
||||||
zones = []
|
|
||||||
for zone in ifc_file.by_type("IfcZone"):
|
|
||||||
for rel in zone.IsGroupedBy:
|
|
||||||
zones.extend([(zone, space) for space in rel.RelatedObjects])
|
|
||||||
return zones
|
|
||||||
|
|
||||||
|
|
||||||
def get_types(ifc_file):
|
|
||||||
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
|
|
||||||
|
|
||||||
|
|
||||||
def get_elements(ifc_file):
|
|
||||||
elements = set()
|
|
||||||
for element_type in ifcopenshell.util.fm.get_fmhem_types(ifc_file):
|
|
||||||
elements.update(ifcopenshell.util.element.get_types(element_type))
|
|
||||||
return elements
|
|
||||||
|
|
||||||
|
|
||||||
def get_systems(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcSystem")
|
|
||||||
|
|
||||||
|
|
||||||
def get_actor_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.TheActor.Name,
|
|
||||||
"Name": element.TheActor.Name,
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"Email": get_actor_address(element, "ElectronicMailAddresses"),
|
|
||||||
"Phone": get_actor_address(element, "TelephoneNumbers"),
|
|
||||||
"CompanyURL": get_actor_address(element, "WWWHomePageURL"),
|
|
||||||
"Department": get_actor_address(element, "InternalLocation"),
|
|
||||||
"Address1": get_actor_address(element, "AddressLines"),
|
|
||||||
"Address2": get_actor_address(element, "Town"),
|
|
||||||
"StateRegion": get_actor_address(element, "Region"),
|
|
||||||
"PostalCode": get_actor_address(element, "PostalCode"),
|
|
||||||
"Country": get_actor_address(element, "Country"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_facility_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
|
|
||||||
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(ifc_file.by_type("IfcProject")[0]),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
|
||||||
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
|
||||||
"ModelBuildingID": element.GlobalId,
|
|
||||||
"LinearUnits": "millimeters",
|
|
||||||
"AreaUnits": "square meters",
|
|
||||||
"AreaMeasurement": "BIM Software",
|
|
||||||
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_storey_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"Category": "Level",
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": element.is_a(),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"Elevation": ifcopenshell.util.placement.get_storey_elevation(element),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_space_data(ifc_file, element):
|
|
||||||
psets = ifcopenshell.util.element.get_psets(element)
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"Description": element.LongName,
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
|
|
||||||
"AreaNet": get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_zone_data(ifc_file, element):
|
|
||||||
zone, space = element
|
|
||||||
return {
|
|
||||||
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
|
|
||||||
"Name": zone.Name,
|
|
||||||
"SpaceName": space.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(zone),
|
|
||||||
"AuthorDate": get_owner_creation_date(zone),
|
|
||||||
"ModelSoftware": get_owner_application(zone),
|
|
||||||
"ModelID": zone.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"Description": element.Description,
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
|
||||||
"ModelTag": element.Tag,
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_element_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
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"TypeName": ifcopenshell.util.element.get_type(element).Name,
|
|
||||||
"SpaceName": space_name,
|
|
||||||
"SystemName": system,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"Description": element.Description,
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_name(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return element.OwnerHistory.OwningUser.TheOrganization.Name
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_creation_date(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_application(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return 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 get_classification(element):
|
|
||||||
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_actor_address(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):
|
|
||||||
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)
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# 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 os
|
||||||
|
import re
|
||||||
|
import csv
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
try:
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import PatternFill
|
||||||
|
except:
|
||||||
|
pass # No XLSX support
|
||||||
|
|
||||||
|
try:
|
||||||
|
from odf.opendocument import OpenDocumentSpreadsheet
|
||||||
|
from odf.style import Style, TableCellProperties
|
||||||
|
from odf.table import Table, TableRow, TableCell
|
||||||
|
from odf.text import P
|
||||||
|
except:
|
||||||
|
pass # No ODF support
|
||||||
|
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
def __init__(self, preset="basic"):
|
||||||
|
self.file = None
|
||||||
|
self.preset = preset
|
||||||
|
self.categories = {}
|
||||||
|
self.get_category_elements = {}
|
||||||
|
self.get_element_data = {}
|
||||||
|
self.get_custom_element_data = {}
|
||||||
|
self.duplicate_keys = []
|
||||||
|
|
||||||
|
if isinstance(preset, str):
|
||||||
|
module = importlib.import_module(f"ifcfm.{preset}")
|
||||||
|
self.get_category_elements = getattr(module, "get_category_elements")
|
||||||
|
self.get_element_data = getattr(module, "get_element_data")
|
||||||
|
else:
|
||||||
|
self.get_category_elements = getattr(preset, "get_category_elements")
|
||||||
|
self.get_element_data = getattr(preset, "get_element_data")
|
||||||
|
|
||||||
|
def parse(self, ifc_file):
|
||||||
|
for category_name, get_category_elements in self.get_category_elements.items():
|
||||||
|
self.categories.setdefault(category_name, {})
|
||||||
|
for element in get_category_elements(ifc_file):
|
||||||
|
get_element_data = self.get_element_data[category_name]
|
||||||
|
|
||||||
|
if isinstance(get_element_data, dict):
|
||||||
|
data = {}
|
||||||
|
for key, query in get_element_data.items():
|
||||||
|
data[key] = ifcopenshell.util.selector.get_element_value(element, query)
|
||||||
|
else:
|
||||||
|
data = get_element_data(ifc_file, element) or {}
|
||||||
|
|
||||||
|
get_custom_element_data = self.get_custom_element_data.get(category_name, lambda x, y: None)
|
||||||
|
if isinstance(get_element_data, dict):
|
||||||
|
custom_data = {}
|
||||||
|
for key, query in get_custom_element_data.items():
|
||||||
|
custom_data[key] = ifcopenshell.util.selector.get_element_value(element, query)
|
||||||
|
else:
|
||||||
|
custom_data = get_custom_element_data(ifc_file, element) or {}
|
||||||
|
|
||||||
|
data.update(custom_data)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
key = data["key"]
|
||||||
|
del data["key"]
|
||||||
|
if key in self.categories[category_name]:
|
||||||
|
self.duplicate_keys.append((self.categories[category_name][key], data))
|
||||||
|
self.categories[category_name][key] = data
|
||||||
|
|
||||||
|
|
||||||
|
class Writer:
|
||||||
|
def __init__(self, parser, colours=None):
|
||||||
|
self.parser = parser
|
||||||
|
if colours:
|
||||||
|
self.colours = colours
|
||||||
|
else:
|
||||||
|
self.colours = {
|
||||||
|
"h": "dddddd", # Header data
|
||||||
|
"p": "dc8774", # Primary identification data
|
||||||
|
"s": "b8dd73", # Secondary asset data
|
||||||
|
"r": "eda786", # Internal reference
|
||||||
|
"e": "96c7d0", # External / autogenerated data
|
||||||
|
"o": "ddb873", # Conditional / optional data
|
||||||
|
"n": "eeeeee", # Other data
|
||||||
|
"b": "000000", # Not in scope
|
||||||
|
}
|
||||||
|
|
||||||
|
if isinstance(self.parser.preset, str):
|
||||||
|
module = importlib.import_module(f"ifcfm.{self.parser.preset}")
|
||||||
|
self.config = getattr(module, "config")
|
||||||
|
else:
|
||||||
|
self.config = getattr(self.parser.preset, "config")
|
||||||
|
|
||||||
|
|
||||||
|
def write(self, null="N/A", empty="-", bool_true="YES", bool_false="NO"):
|
||||||
|
self.categories = {}
|
||||||
|
for category, data in self.parser.categories.items():
|
||||||
|
headers = self.config.get(category, {}).get("headers", [])
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
self.categories[category] = {"headers": headers, "rows": []}
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not headers:
|
||||||
|
headers = list(data[list(data.keys())[0]].keys())
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for row in data.values():
|
||||||
|
processed_row = []
|
||||||
|
for header in headers:
|
||||||
|
value = row[header]
|
||||||
|
if value is None:
|
||||||
|
value = null
|
||||||
|
elif value == "":
|
||||||
|
value = empty
|
||||||
|
elif value is True:
|
||||||
|
value = bool_true
|
||||||
|
elif value is False:
|
||||||
|
value = bool_false
|
||||||
|
processed_row.append(value)
|
||||||
|
rows.append(processed_row)
|
||||||
|
|
||||||
|
sort = self.config.get(category, {}).get("sort", None)
|
||||||
|
if sort:
|
||||||
|
|
||||||
|
def natural_sort(value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
||||||
|
return [convert(c) for c in re.split("([0-9]+)", value)]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Sort least important keys first, then more important keys.
|
||||||
|
# https://stackoverflow.com/questions/11476371/sort-by-multiple-keys-using-different-orderings
|
||||||
|
for sort_data in reversed(sort):
|
||||||
|
i = headers.index(sort_data["name"])
|
||||||
|
reverse = sort_data["order"] == "DESC"
|
||||||
|
rows = sorted(rows, key=lambda x: natural_sort(x[i]), reverse=reverse)
|
||||||
|
self.categories[category] = {"headers": headers, "rows": rows}
|
||||||
|
|
||||||
|
def write_csv(self, output, delimiter=","):
|
||||||
|
filename = None
|
||||||
|
if len(self.categories.keys()) == 1 and "." in os.path.basename(output):
|
||||||
|
filename = output
|
||||||
|
for category, data in self.categories.items():
|
||||||
|
category_filename = filename or os.path.join(output, f"{category}.csv")
|
||||||
|
with open(category_filename, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f, delimiter=delimiter)
|
||||||
|
writer.writerow(data["headers"])
|
||||||
|
for row in data["rows"]:
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
def write_ods(self, output):
|
||||||
|
doc = OpenDocumentSpreadsheet()
|
||||||
|
|
||||||
|
for key, value in self.colours.items():
|
||||||
|
style = Style(name=key, family="table-cell")
|
||||||
|
style.addElement(TableCellProperties(backgroundcolor="#" + value))
|
||||||
|
doc.automaticstyles.addElement(style)
|
||||||
|
|
||||||
|
for category, data in self.categories.items():
|
||||||
|
colours = self.config.get(category, {}).get("colours", [])
|
||||||
|
|
||||||
|
table = Table(name=category)
|
||||||
|
tr = TableRow()
|
||||||
|
for header in data["headers"]:
|
||||||
|
tc = TableCell(valuetype="string", stylename="h")
|
||||||
|
tc.addElement(P(text=header))
|
||||||
|
tr.addElement(tc)
|
||||||
|
table.addElement(tr)
|
||||||
|
for row in data["rows"]:
|
||||||
|
tr = TableRow()
|
||||||
|
c = 0
|
||||||
|
for col in row:
|
||||||
|
if c >= len(colours):
|
||||||
|
cell_format = "n"
|
||||||
|
else:
|
||||||
|
cell_format = colours[c]
|
||||||
|
tc = TableCell(valuetype="string", stylename=cell_format)
|
||||||
|
tc.addElement(P(text=str(col)))
|
||||||
|
tr.addElement(tc)
|
||||||
|
c += 1
|
||||||
|
table.addElement(tr)
|
||||||
|
doc.spreadsheet.addElement(table)
|
||||||
|
|
||||||
|
if len(output) > 4 and output[-4:].lower() == ".ods":
|
||||||
|
output = output[0:-4]
|
||||||
|
|
||||||
|
doc.save(output, True)
|
||||||
|
|
||||||
|
def write_xlsx(self, output):
|
||||||
|
workbook = Workbook()
|
||||||
|
|
||||||
|
cell_formats = {}
|
||||||
|
for key, value in self.colours.items():
|
||||||
|
fill = PatternFill(start_color=value, end_color=value, fill_type="solid")
|
||||||
|
cell_formats[key] = fill
|
||||||
|
|
||||||
|
for category, data in self.categories.items():
|
||||||
|
colours = self.config.get(category, {}).get("colours", [])
|
||||||
|
|
||||||
|
if category in workbook.sheetnames:
|
||||||
|
worksheet = workbook[category]
|
||||||
|
else:
|
||||||
|
worksheet = workbook.create_sheet(category)
|
||||||
|
|
||||||
|
r = 1 # Openpyxl uses 1-based indexing
|
||||||
|
c = 1
|
||||||
|
for header in data["headers"]:
|
||||||
|
cell = worksheet.cell(row=r, column=c, value=header)
|
||||||
|
cell.fill = cell_formats["h"]
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
r += 1
|
||||||
|
for row in data["rows"]:
|
||||||
|
c = 1
|
||||||
|
for col in row:
|
||||||
|
if c > len(colours): # Adjusted the comparison
|
||||||
|
cell_format = "n"
|
||||||
|
else:
|
||||||
|
cell_format = colours[c - 1] # Adjusted the indexing
|
||||||
|
cell = worksheet.cell(row=r, column=c, value=col)
|
||||||
|
cell.fill = cell_formats[cell_format]
|
||||||
|
c += 1
|
||||||
|
r += 1
|
||||||
|
|
||||||
|
workbook.save(output)
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
def get_actors(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcActor")
|
||||||
|
|
||||||
|
|
||||||
|
def get_facilities(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcBuilding")
|
||||||
|
|
||||||
|
|
||||||
|
def get_storeys(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcBuildingStorey")
|
||||||
|
|
||||||
|
|
||||||
|
def get_spaces(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcSpace")
|
||||||
|
|
||||||
|
|
||||||
|
def get_zones(ifc_file):
|
||||||
|
zones = []
|
||||||
|
for zone in ifc_file.by_type("IfcZone"):
|
||||||
|
for rel in zone.IsGroupedBy:
|
||||||
|
zones.extend([(zone, space) for space in rel.RelatedObjects])
|
||||||
|
return zones
|
||||||
|
|
||||||
|
|
||||||
|
def get_types(ifc_file):
|
||||||
|
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_elements(ifc_file):
|
||||||
|
elements = set()
|
||||||
|
for element_type in ifcopenshell.util.fm.get_fmhem_types(ifc_file):
|
||||||
|
elements.update(ifcopenshell.util.element.get_types(element_type))
|
||||||
|
return elements
|
||||||
|
|
||||||
|
|
||||||
|
def get_systems(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcSystem")
|
||||||
|
|
||||||
|
|
||||||
|
def get_actor_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.TheActor.Name,
|
||||||
|
"Name": element.TheActor.Name,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"Email": get_actor_address(element, "ElectronicMailAddresses"),
|
||||||
|
"Phone": get_actor_address(element, "TelephoneNumbers"),
|
||||||
|
"CompanyURL": get_actor_address(element, "WWWHomePageURL"),
|
||||||
|
"Department": get_actor_address(element, "InternalLocation"),
|
||||||
|
"Address": get_actor_address(element, "AddressLines"),
|
||||||
|
"Town": get_actor_address(element, "Town"),
|
||||||
|
"Region": get_actor_address(element, "Region"),
|
||||||
|
"PostalCode": get_actor_address(element, "PostalCode"),
|
||||||
|
"Country": get_actor_address(element, "Country"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_facility_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
|
||||||
|
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
||||||
|
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
||||||
|
"ModelBuildingID": element.GlobalId,
|
||||||
|
"LinearUnits": "millimeters",
|
||||||
|
"AreaUnits": "square meters",
|
||||||
|
"AreaMeasurement": "BIM Software",
|
||||||
|
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_storey_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Category": "Level",
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelObject": element.is_a(),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
"Elevation": ifcopenshell.util.placement.get_storey_elevation(element),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_space_data(ifc_file, element):
|
||||||
|
psets = ifcopenshell.util.element.get_psets(element)
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Description": element.LongName,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
|
||||||
|
"AreaNet": get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_zone_data(ifc_file, element):
|
||||||
|
zone, space = element
|
||||||
|
return {
|
||||||
|
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
|
||||||
|
"Name": zone.Name,
|
||||||
|
"SpaceName": space.Name,
|
||||||
|
"AuthorOrganizationName": get_owner_name(zone),
|
||||||
|
"AuthorDate": get_owner_creation_date(zone),
|
||||||
|
"ModelSoftware": get_owner_application(zone),
|
||||||
|
"ModelID": zone.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Description": element.Description,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||||
|
"ModelTag": element.Tag,
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_element_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
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"TypeName": ifcopenshell.util.element.get_type(element).Name,
|
||||||
|
"SpaceName": space_name,
|
||||||
|
"SystemName": system,
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Description": element.Description,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_owner_application(element),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_owner_name(element):
|
||||||
|
if not getattr(element, "OwnerHistory", None):
|
||||||
|
return
|
||||||
|
return element.OwnerHistory.OwningUser.TheOrganization.Name
|
||||||
|
|
||||||
|
|
||||||
|
def get_owner_creation_date(element):
|
||||||
|
if not getattr(element, "OwnerHistory", None):
|
||||||
|
return
|
||||||
|
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def get_owner_application(element):
|
||||||
|
if not getattr(element, "OwnerHistory", None):
|
||||||
|
return
|
||||||
|
return 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 get_classification(element):
|
||||||
|
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_actor_address(element, name):
|
||||||
|
actors = []
|
||||||
|
if element.TheActor.is_a("IfcOrganization") or element.TheActor.is_a("IfcPerson"):
|
||||||
|
actors = [element.TheActor]
|
||||||
|
elif element.TheActor.is_a("IfcPersonAndOrganization"):
|
||||||
|
actors = [element.TheActor.TheOrganization, element.TheActor.ThePerson]
|
||||||
|
for actor in actors:
|
||||||
|
for address in actor.Addresses or []:
|
||||||
|
if hasattr(address, name) and getattr(address, name, None):
|
||||||
|
result = getattr(address, name)
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
get_category_elements = {
|
||||||
|
"Actors": get_actors,
|
||||||
|
"Facilities": get_facilities,
|
||||||
|
"Storeys": get_storeys,
|
||||||
|
"Spaces": get_spaces,
|
||||||
|
"Zones": get_zones,
|
||||||
|
"Types": get_types,
|
||||||
|
"Elements": get_elements,
|
||||||
|
"Systems": get_systems,
|
||||||
|
}
|
||||||
|
|
||||||
|
get_element_data = {
|
||||||
|
"Actors": get_actor_data,
|
||||||
|
"Facilities": get_facility_data,
|
||||||
|
"Storeys": get_storey_data,
|
||||||
|
"Spaces": get_space_data,
|
||||||
|
"Zones": get_zone_data,
|
||||||
|
"Types": get_type_data,
|
||||||
|
"Elements": get_element_data,
|
||||||
|
"Systems": get_system_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"Actors": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Category",
|
||||||
|
"Email",
|
||||||
|
"Phone",
|
||||||
|
"CompanyURL",
|
||||||
|
"Department",
|
||||||
|
"Address1",
|
||||||
|
"Address2",
|
||||||
|
"StateRegion",
|
||||||
|
"PostalCode",
|
||||||
|
"Country",
|
||||||
|
],
|
||||||
|
"colours": "ppssssssss",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Facilities": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"ProjectName",
|
||||||
|
"SiteName",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelProjectID",
|
||||||
|
"ModelSiteID",
|
||||||
|
"ModelBuildingID",
|
||||||
|
"LinearUnits",
|
||||||
|
"AreaUnits",
|
||||||
|
"AreaMeasurement",
|
||||||
|
"Phase",
|
||||||
|
],
|
||||||
|
"colours": "ppppreeeeessss",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Storeys": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelID",
|
||||||
|
"Elevation",
|
||||||
|
],
|
||||||
|
"colours": "ppreeees",
|
||||||
|
"sort": [{"name": "Elevation", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Spaces": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"LevelName",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelID",
|
||||||
|
"AreaGross",
|
||||||
|
"AreaNet",
|
||||||
|
],
|
||||||
|
"colours": "ppprreeess",
|
||||||
|
"sort": [{"name": "LevelName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Zones": {
|
||||||
|
"headers": ["Name", "SpaceName", "AuthorOrganizationName", "AuthorDate", "ModelSoftware", "ModelID"],
|
||||||
|
"colours": "prreee",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Types": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelTag",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "pppreeeee",
|
||||||
|
"sort": [{"name": "ModelObject", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Elements": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"TypeName",
|
||||||
|
"SpaceName",
|
||||||
|
"SystemName",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "prrrreeee",
|
||||||
|
"sort": [{"name": "TypeName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Systems": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "pppreee",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,625 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
def get_contacts(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcPersonAndOrganization")
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
zones = []
|
||||||
|
for zone in ifc_file.by_type("IfcZone"):
|
||||||
|
for rel in zone.IsGroupedBy:
|
||||||
|
zones.extend([(zone, space) for space in rel.RelatedObjects])
|
||||||
|
return zones
|
||||||
|
|
||||||
|
|
||||||
|
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 ifcopenshell.util.fm.get_cobie_types(ifc_file):
|
||||||
|
elements.update(ifcopenshell.util.element.get_types(element_type))
|
||||||
|
return elements
|
||||||
|
|
||||||
|
|
||||||
|
def get_systems(ifc_file):
|
||||||
|
return ifc_file.by_type("IfcSystem")
|
||||||
|
|
||||||
|
|
||||||
|
def get_contact_data(ifc_file, element):
|
||||||
|
email = get_email_from_pao(element)
|
||||||
|
|
||||||
|
history = None
|
||||||
|
created_by = None
|
||||||
|
created_on = None
|
||||||
|
histories = ifc_file.by_type("IfcOwnerHistory")
|
||||||
|
if histories:
|
||||||
|
history = sorted(histories, key=lambda x: x.id())[-1]
|
||||||
|
|
||||||
|
roles = []
|
||||||
|
for actor in [element, element.ThePerson, element.TheOrganization]:
|
||||||
|
for role in actor.Roles or []:
|
||||||
|
if role.Role == "USERDEFINED":
|
||||||
|
if role.UserDefinedRole:
|
||||||
|
roles.append(role.UserDefinedRole)
|
||||||
|
else:
|
||||||
|
roles.append(role.Role)
|
||||||
|
|
||||||
|
organization = element.TheOrganization
|
||||||
|
person = element.ThePerson
|
||||||
|
|
||||||
|
department = get_pao_address(element, "InternalLocation")
|
||||||
|
if not department:
|
||||||
|
for rel in organization.Relates:
|
||||||
|
for org in rel.RelatedOrganizations:
|
||||||
|
if val(org.Name):
|
||||||
|
department = org.Name
|
||||||
|
|
||||||
|
return {
|
||||||
|
"key": email,
|
||||||
|
"Email": email,
|
||||||
|
"CreatedBy": get_email_from_history(history) if history else None,
|
||||||
|
"CreatedOn": ifcopenshell.util.date.ifc2datetime(history.CreationDate).isoformat() if history else None,
|
||||||
|
"Category": ",".join(roles),
|
||||||
|
"Company": getattr(organization, "Name", None),
|
||||||
|
"Phone": get_pao_address(element, "TelephoneNumbers"),
|
||||||
|
"ExternalSystem": history.OwningApplication.ApplicationFullName if history else None,
|
||||||
|
"ExternalObject": element.is_a(),
|
||||||
|
"ExternalIdentifier": email,
|
||||||
|
"Department": department,
|
||||||
|
"OrganizationCode": getattr(organization, "Id", getattr(organization, "Identification", None))
|
||||||
|
or organization.Name,
|
||||||
|
"GivenName": getattr(person, "GivenName", None),
|
||||||
|
"FamilyName": getattr(person, "FamilyName", None),
|
||||||
|
"Street": get_pao_address(element, "AddressLines"),
|
||||||
|
"PostalBox": get_pao_address(element, "PostalBox"),
|
||||||
|
"Town": get_pao_address(element, "Town"),
|
||||||
|
"StateRegion": get_pao_address(element, "Region"),
|
||||||
|
"PostalCode": get_pao_address(element, "PostalCode"),
|
||||||
|
"Country": get_pao_address(element, "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) or val(site.LongName) or site.GlobalId
|
||||||
|
site_description = val(site.Description) or val(site.LongName) or val(site.Name)
|
||||||
|
|
||||||
|
project = None
|
||||||
|
project_name = None
|
||||||
|
project_description = None
|
||||||
|
try:
|
||||||
|
project = ifc_file.by_type("IfcProject")[0]
|
||||||
|
project_name = val(project.Name) or val(project.LongName) or project.GlobalId
|
||||||
|
project_description = val(project.Description) or val(project.LongName) or val(project.Name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
name = val(element.Name) or val(element.LongName)
|
||||||
|
if not name:
|
||||||
|
name = val(project.Name) or val(project.LongName)
|
||||||
|
if not name and site:
|
||||||
|
name = val(site.Name) or val(site.LongName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"key": name,
|
||||||
|
"Name": name,
|
||||||
|
"CreatedBy": get_created_by(element),
|
||||||
|
"CreatedOn": get_created_on(element),
|
||||||
|
"Category": get_category(element),
|
||||||
|
"ProjectName": project_name,
|
||||||
|
"SiteName": site_name,
|
||||||
|
"LinearUnits": get_unit_name(ifc_file, "LENGTHUNIT"),
|
||||||
|
"AreaUnits": get_unit_name(ifc_file, "AREAUNIT"),
|
||||||
|
"VolumeUnits": get_unit_name(ifc_file, "VOLUMEUNIT"),
|
||||||
|
"CurrencyUnit": get_unit_name(ifc_file, "IfcMonetaryUnit"),
|
||||||
|
"AreaMeasurement": get_area_measurement(element),
|
||||||
|
"ExternalSystem": get_external_system(element),
|
||||||
|
"ExternalProjectObject": "IfcProject",
|
||||||
|
"ExternalProjectIdentifier": project.GlobalId if project else ifcopenshell.guid.new(),
|
||||||
|
"ExternalSiteObject": "IfcSite",
|
||||||
|
"ExternalSiteIdentifier": site.GlobalId if site else ifcopenshell.guid.new(),
|
||||||
|
"ExternalFacilityObject": "IfcBuilding",
|
||||||
|
"ExternalFacilityIdentifier": element.GlobalId,
|
||||||
|
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||||
|
"ProjectDescription": project_description,
|
||||||
|
"SiteDescription": site_description,
|
||||||
|
"Phase": val(project.Phase) if project else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_floor_data(ifc_file, element):
|
||||||
|
external_object = element.is_a()
|
||||||
|
if external_object.ObjectType and external_object.ObjectType.lower() in ("site", "ifcsite"):
|
||||||
|
external_object = "IfcSite"
|
||||||
|
|
||||||
|
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):
|
||||||
|
if height is not None:
|
||||||
|
break
|
||||||
|
for name, value in props.items():
|
||||||
|
if name in height_names and val(value):
|
||||||
|
height = str(value)
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"key": var(element.Name),
|
||||||
|
"Name": var(element.Name),
|
||||||
|
"CreatedBy": get_created_by(element),
|
||||||
|
"CreatedOn": get_created_on(element),
|
||||||
|
"Category": get_category(element),
|
||||||
|
"ExternalSystem": get_external_system(element),
|
||||||
|
"ExternalObject": external_object,
|
||||||
|
"ExternalIdentifier": element.GlobalId,
|
||||||
|
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||||
|
"Elevation": val(str(getattr(element, "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):
|
||||||
|
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 {
|
||||||
|
"key": val(element.Name),
|
||||||
|
"Name": val(element.Name),
|
||||||
|
"CreatedBy": get_created_by(element),
|
||||||
|
"CreatedOn": get_created_on(element),
|
||||||
|
"Category": get_category(element),
|
||||||
|
"FloorName": floor_name,
|
||||||
|
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||||
|
"ExternalSystem": get_external_system(element),
|
||||||
|
"ExternalObject": element.is_a(),
|
||||||
|
"ExternalIdentifier": element.GlobalId,
|
||||||
|
"RoomTag": room_tag,
|
||||||
|
"UsableHeight": usable_height,
|
||||||
|
"GrossArea": gross_area,
|
||||||
|
"NetArea": net_area,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_zone_data(ifc_file, element):
|
||||||
|
zone, space = element
|
||||||
|
return {
|
||||||
|
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
|
||||||
|
"Name": zone.Name,
|
||||||
|
"SpaceName": space.Name,
|
||||||
|
"AuthorOrganizationName": get_owner_name(zone),
|
||||||
|
"AuthorDate": get_owner_creation_date(zone),
|
||||||
|
"ModelSoftware": get_external_system(zone),
|
||||||
|
"ModelID": zone.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Description": element.Description,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_external_system(element),
|
||||||
|
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||||
|
"ModelTag": element.Tag,
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_element_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
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"TypeName": ifcopenshell.util.element.get_type(element).Name,
|
||||||
|
"SpaceName": space_name,
|
||||||
|
"SystemName": system,
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_external_system(element),
|
||||||
|
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_data(ifc_file, element):
|
||||||
|
return {
|
||||||
|
"key": element.Name,
|
||||||
|
"Name": element.Name,
|
||||||
|
"Description": element.Description,
|
||||||
|
"Category": get_classification(element),
|
||||||
|
"AuthorOrganizationName": get_owner_name(element),
|
||||||
|
"AuthorDate": get_owner_creation_date(element),
|
||||||
|
"ModelSoftware": get_external_system(element),
|
||||||
|
"ModelID": element.GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_unit_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_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)
|
||||||
|
|
||||||
|
|
||||||
|
def get_email_from_pao(pao):
|
||||||
|
for address in pao.ThePerson.Addresses or []:
|
||||||
|
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
|
||||||
|
return address.ElectronicMailAddresses[0]
|
||||||
|
|
||||||
|
for address in pao.TheOrganization.Addresses or []:
|
||||||
|
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
|
||||||
|
return address.ElectronicMailAddresses[0]
|
||||||
|
|
||||||
|
person_id = getattr(pao.ThePerson, "Identification", getattr(pao.ThePerson, "Id", None))
|
||||||
|
if person_id:
|
||||||
|
return person_id
|
||||||
|
|
||||||
|
organization_id = getattr(pao.TheOrganization, "Identification", getattr(pao.TheOrganization, "Id", None))
|
||||||
|
if organization_id:
|
||||||
|
return organization_id
|
||||||
|
|
||||||
|
if pao.ThePerson.GivenName and pao.ThePerson.FamilyName and pao.TheOrganization.Name:
|
||||||
|
return pao.ThePerson.GivenName + pao.ThePerson.FamilyName + "@" + pao.TheOrganization.Name + ".com"
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def get_category(element):
|
||||||
|
references = list(ifcopenshell.util.classification.get_references(element))
|
||||||
|
results = []
|
||||||
|
for reference in references:
|
||||||
|
if reference.is_a("IfcClassification"):
|
||||||
|
results.append(reference.Name)
|
||||||
|
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)
|
||||||
|
elif reference.ReferencedSource and val(reference.ReferencedSource.Name):
|
||||||
|
results.append(reference.ReferencedSource.Name)
|
||||||
|
elif val(reference.Location):
|
||||||
|
results.append(reference.Location)
|
||||||
|
if results:
|
||||||
|
return ",".join(results)
|
||||||
|
|
||||||
|
category_props = [
|
||||||
|
("Assembly Code", "Assembly Description"),
|
||||||
|
("Category Code", "Category Description"),
|
||||||
|
("Classification Code", "Classification Description"),
|
||||||
|
("OmniClass Number", "OmniClass Title"),
|
||||||
|
("Uniclass Code", "Uniclass Description"),
|
||||||
|
]
|
||||||
|
|
||||||
|
psets = ifcopenshell.util.element.get_psets(element)
|
||||||
|
properties = {}
|
||||||
|
if psets:
|
||||||
|
for _, props in psets.items():
|
||||||
|
properties.update(props)
|
||||||
|
|
||||||
|
for code, description in category_props:
|
||||||
|
code = val(properties.get(code, None))
|
||||||
|
if code:
|
||||||
|
description = val(properties.get(description, None))
|
||||||
|
if code and description:
|
||||||
|
results.append(code + " : " + description)
|
||||||
|
else:
|
||||||
|
results.append(code)
|
||||||
|
if results:
|
||||||
|
return ",".join(results)
|
||||||
|
|
||||||
|
return val(getattr(element, "ObjectType", None))
|
||||||
|
|
||||||
|
|
||||||
|
def get_pao_address(element, name):
|
||||||
|
for actor in [element.TheActor.ThePerson, element.TheActor.TheOrganization]:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
get_category_elements = {
|
||||||
|
"Contact": get_contacts,
|
||||||
|
"Facility": get_facilities,
|
||||||
|
"Floor": get_floors,
|
||||||
|
"Space": get_spaces,
|
||||||
|
"Zone": get_zones,
|
||||||
|
"Type": get_types,
|
||||||
|
"Component": get_components,
|
||||||
|
"System": get_systems,
|
||||||
|
}
|
||||||
|
|
||||||
|
get_element_data = {
|
||||||
|
"Contact": get_contact_data,
|
||||||
|
"Facility": get_facility_data,
|
||||||
|
"Floor": get_floor_data,
|
||||||
|
"Space": get_space_data,
|
||||||
|
"Zone": get_zone_data,
|
||||||
|
"Type": get_type_data,
|
||||||
|
"Component": get_component_data,
|
||||||
|
"System": get_system_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"Actors": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Category",
|
||||||
|
"Email",
|
||||||
|
"Phone",
|
||||||
|
"CompanyURL",
|
||||||
|
"Department",
|
||||||
|
"Address1",
|
||||||
|
"Address2",
|
||||||
|
"StateRegion",
|
||||||
|
"PostalCode",
|
||||||
|
"Country",
|
||||||
|
],
|
||||||
|
"colours": "ppssssssss",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Facilities": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"ProjectName",
|
||||||
|
"SiteName",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelProjectID",
|
||||||
|
"ModelSiteID",
|
||||||
|
"ModelBuildingID",
|
||||||
|
"LinearUnits",
|
||||||
|
"AreaUnits",
|
||||||
|
"AreaMeasurement",
|
||||||
|
"Phase",
|
||||||
|
],
|
||||||
|
"colours": "ppppreeeeessss",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Storeys": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelID",
|
||||||
|
"Elevation",
|
||||||
|
],
|
||||||
|
"colours": "ppreeees",
|
||||||
|
"sort": [{"name": "Elevation", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Spaces": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"LevelName",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelID",
|
||||||
|
"AreaGross",
|
||||||
|
"AreaNet",
|
||||||
|
],
|
||||||
|
"colours": "ppprreeess",
|
||||||
|
"sort": [{"name": "LevelName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Zones": {
|
||||||
|
"headers": ["Name", "SpaceName", "AuthorOrganizationName", "AuthorDate", "ModelSoftware", "ModelID"],
|
||||||
|
"colours": "prreee",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Types": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelTag",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "pppreeeee",
|
||||||
|
"sort": [{"name": "ModelObject", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Elements": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"TypeName",
|
||||||
|
"SpaceName",
|
||||||
|
"SystemName",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelObject",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "prrrreeee",
|
||||||
|
"sort": [{"name": "TypeName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
"Systems": {
|
||||||
|
"headers": [
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"Category",
|
||||||
|
"AuthorOrganizationName",
|
||||||
|
"AuthorDate",
|
||||||
|
"ModelSoftware",
|
||||||
|
"ModelID",
|
||||||
|
],
|
||||||
|
"colours": "pppreee",
|
||||||
|
"sort": [{"name": "Name", "order": "ASC"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,658 +0,0 @@
|
|||||||
# IfcFM - IFC for facility management
|
|
||||||
# Copyright (C) 2021 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 datetime
|
|
||||||
import ifcopenshell
|
|
||||||
import ifcopenshell.util.fm
|
|
||||||
import ifcopenshell.util.selector
|
|
||||||
import ifcopenshell.util.date
|
|
||||||
import ifcopenshell.util.schema
|
|
||||||
import ifcopenshell.util.system
|
|
||||||
import ifcopenshell.util.placement
|
|
||||||
import ifcopenshell.util.classification
|
|
||||||
|
|
||||||
|
|
||||||
class Parser2:
|
|
||||||
def __init__(self, preset="BASIC"):
|
|
||||||
self.file = None
|
|
||||||
self.categories = {}
|
|
||||||
self.get_category_elements = {}
|
|
||||||
self.get_element_data = {}
|
|
||||||
self.get_custom_element_data = {}
|
|
||||||
self.duplicate_keys = []
|
|
||||||
|
|
||||||
if preset == "BASIC":
|
|
||||||
self.get_category_elements = {
|
|
||||||
"actors": get_actors,
|
|
||||||
"facilities": get_facilities,
|
|
||||||
"storeys": get_storeys,
|
|
||||||
"spaces": get_spaces,
|
|
||||||
"zones": get_zones,
|
|
||||||
"types": get_types,
|
|
||||||
"elements": get_elements,
|
|
||||||
"systems": get_systems,
|
|
||||||
}
|
|
||||||
self.get_element_data = {
|
|
||||||
"actors": get_actor_data,
|
|
||||||
"facilities": get_facility_data,
|
|
||||||
"storeys": get_storey_data,
|
|
||||||
"spaces": get_space_data,
|
|
||||||
"zones": get_zone_data,
|
|
||||||
"types": get_type_data,
|
|
||||||
"elements": get_element_data,
|
|
||||||
"systems": get_system_data,
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(self, ifc_file):
|
|
||||||
for category_name, get_category_elements in self.get_category_elements.items():
|
|
||||||
self.categories.setdefault(category_name, {})
|
|
||||||
for element in get_category_elements(ifc_file):
|
|
||||||
data = self.get_element_data[category_name](ifc_file, element) or {}
|
|
||||||
custom_data = (
|
|
||||||
self.get_custom_element_data.get(category_name, lambda x, y: None)(ifc_file, element) or {}
|
|
||||||
)
|
|
||||||
data.update(custom_data)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
if data["key"] in self.categories[category_name]:
|
|
||||||
self.duplicate_keys.append((self.categories[category_name][data["key"]], data))
|
|
||||||
self.categories[category_name][data["key"]] = data
|
|
||||||
|
|
||||||
|
|
||||||
def get_actors(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcActor")
|
|
||||||
|
|
||||||
|
|
||||||
def get_facilities(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcBuilding")
|
|
||||||
|
|
||||||
|
|
||||||
def get_storeys(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcBuildingStorey")
|
|
||||||
|
|
||||||
|
|
||||||
def get_spaces(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcSpace")
|
|
||||||
|
|
||||||
|
|
||||||
def get_zones(ifc_file):
|
|
||||||
zones = []
|
|
||||||
for zone in ifc_file.by_type("IfcZone"):
|
|
||||||
for rel in zone.IsGroupedBy:
|
|
||||||
zones.extend([(zone, space) for space in rel.RelatedObjects])
|
|
||||||
return zones
|
|
||||||
|
|
||||||
|
|
||||||
def get_types(ifc_file):
|
|
||||||
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
|
|
||||||
|
|
||||||
|
|
||||||
def get_elements(ifc_file):
|
|
||||||
elements = set()
|
|
||||||
for element_type in ifcopenshell.util.fm.get_fmhem_types(ifc_file):
|
|
||||||
elements.update(ifcopenshell.util.element.get_types(element_type))
|
|
||||||
return elements
|
|
||||||
|
|
||||||
|
|
||||||
def get_systems(ifc_file):
|
|
||||||
return ifc_file.by_type("IfcSystem")
|
|
||||||
|
|
||||||
|
|
||||||
def get_actor_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.TheActor.Name,
|
|
||||||
"Name": element.TheActor.Name,
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"Email": get_actor_address(element, "ElectronicMailAddresses"),
|
|
||||||
"Phone": get_actor_address(element, "TelephoneNumbers"),
|
|
||||||
"CompanyURL": get_actor_address(element, "WWWHomePageURL"),
|
|
||||||
"Department": get_actor_address(element, "InternalLocation"),
|
|
||||||
"Address1": get_actor_address(element, "AddressLines"),
|
|
||||||
"Address2": get_actor_address(element, "Town"),
|
|
||||||
"StateRegion": get_actor_address(element, "Region"),
|
|
||||||
"PostalCode": get_actor_address(element, "PostalCode"),
|
|
||||||
"Country": get_actor_address(element, "Country"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_facility_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(ifc_file.by_type("IfcProject")[0]),
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
|
|
||||||
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
|
|
||||||
"LinearUnits": "millimeters",
|
|
||||||
"AreaUnits": "square meters",
|
|
||||||
"AreaMeasurement": "BIM Software",
|
|
||||||
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
|
||||||
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
|
||||||
"ModelBuildingID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_storey_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"Category": "Level",
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": element.is_a(),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"Elevation": ifcopenshell.util.placement.get_storey_elevation(element),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_space_data(ifc_file, element):
|
|
||||||
psets = ifcopenshell.util.element.get_psets(element)
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
|
||||||
"Description": element.LongName,
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
|
|
||||||
"AreaNet": get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_zone_data(ifc_file, element):
|
|
||||||
zone, space = element
|
|
||||||
return {
|
|
||||||
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
|
|
||||||
"Name": zone.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(zone),
|
|
||||||
"AuthorDate": get_owner_creation_date(zone),
|
|
||||||
"SpaceName": space.Name,
|
|
||||||
"ModelSoftware": get_owner_application(zone),
|
|
||||||
"ModelID": zone.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"Description": element.Description,
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
|
||||||
"ModelTag": element.Tag,
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_element_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
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"TypeName": ifcopenshell.util.element.get_type(element).Name,
|
|
||||||
"SpaceName": space_name,
|
|
||||||
"SystemName": system,
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_data(ifc_file, element):
|
|
||||||
return {
|
|
||||||
"key": element.Name,
|
|
||||||
"Name": element.Name,
|
|
||||||
"Description": element.Description,
|
|
||||||
"AuthorOrganizationName": get_owner_name(element),
|
|
||||||
"AuthorDate": get_owner_creation_date(element),
|
|
||||||
"Category": get_classification(element),
|
|
||||||
"ModelSoftware": get_owner_application(element),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_name(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return element.OwnerHistory.OwningUser.TheOrganization.Name
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_creation_date(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
def get_owner_application(element):
|
|
||||||
if not getattr(element, "OwnerHistory", None):
|
|
||||||
return
|
|
||||||
return 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 get_classification(element):
|
|
||||||
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_actor_address(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):
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
|
||||||
def __init__(self, logger):
|
|
||||||
self.logger = logger
|
|
||||||
self.file = None
|
|
||||||
self.categories = {
|
|
||||||
"actors": self.get_actors,
|
|
||||||
"facilities": self.get_facilities,
|
|
||||||
"floors": self.get_floors,
|
|
||||||
"spaces": self.get_spaces,
|
|
||||||
"zones": self.get_zones,
|
|
||||||
"types": self.get_types,
|
|
||||||
"components": self.get_components,
|
|
||||||
"systems": self.get_systems,
|
|
||||||
# "assemblies",
|
|
||||||
# "connections",
|
|
||||||
# "spares",
|
|
||||||
# "resources",
|
|
||||||
# "jobs",
|
|
||||||
# "impacts",
|
|
||||||
"documents": self.get_documents,
|
|
||||||
# "attributes",
|
|
||||||
# "coordinates",
|
|
||||||
# "issues",
|
|
||||||
}
|
|
||||||
# COBie
|
|
||||||
self.custom_parameters = {
|
|
||||||
"types": {
|
|
||||||
"AssetType": lambda e, p: None,
|
|
||||||
"ManufacturerOrganizationName": lambda e, p: None,
|
|
||||||
"ModelNumber": lambda e, p: None,
|
|
||||||
"WarrantyGuarantorParts": lambda e, p: None,
|
|
||||||
"WarrantyDurationParts": lambda e, p: None,
|
|
||||||
"WarrantyGuarantorLabour": lambda e, p: None,
|
|
||||||
"WarrantyDurationLabour": lambda e, p: None,
|
|
||||||
"DurationUnit": lambda e, p: "months",
|
|
||||||
"WarrantyDescription": lambda e, p: None,
|
|
||||||
"ReplacementCost": lambda e, p: None,
|
|
||||||
"ExpectedLife": lambda e, p: None,
|
|
||||||
"NominalLength": lambda e, p: None,
|
|
||||||
"NominalWidth": lambda e, p: None,
|
|
||||||
"NominalHeight": lambda e, p: None,
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"SerialNumber": lambda e, p: None,
|
|
||||||
"InstallationDate": lambda e, p: None,
|
|
||||||
"WarrantyStartDate": lambda e, p: None,
|
|
||||||
"TagNumber": lambda e, p: None,
|
|
||||||
"BarCode": lambda e, p: None,
|
|
||||||
"AssetIdentifier": lambda e, p: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# AOH-BEM
|
|
||||||
self.custom_parameters = {
|
|
||||||
"types": {
|
|
||||||
"ProcurementMethod": lambda e, p: None,
|
|
||||||
"ManufacturerOrganizationName": lambda e, p: None,
|
|
||||||
"SupplierOrganizationName": lambda e, p: None,
|
|
||||||
"ModelNumber": lambda e, p: None,
|
|
||||||
"WarrantyOrganizationName": lambda e, p: None,
|
|
||||||
"WarrantyDuration": lambda e, p: None,
|
|
||||||
"SpecificationSection": lambda e, p: None,
|
|
||||||
"SubmittalID": lambda e, p: None,
|
|
||||||
"ProductURL": lambda e, p: None,
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"InstallationDate": lambda e, p: None,
|
|
||||||
"WarrantyStartDate": lambda e, p: None,
|
|
||||||
"InstalledModelNumber": lambda e, p: None,
|
|
||||||
"SerialNumber": lambda e, p: None,
|
|
||||||
"BarCode": lambda e, p: None,
|
|
||||||
"TagNumber": lambda e, p: None,
|
|
||||||
"OwnerAssetID": lambda e, p: None,
|
|
||||||
"FluidHotFeedName": lambda e, p: None,
|
|
||||||
"FluidColdFeedName": lambda e, p: None,
|
|
||||||
"ElectricPanelName": lambda e, p: None,
|
|
||||||
"ElectricCircuitName": lambda e, p: None,
|
|
||||||
"ControlledByName": lambda e, p: None,
|
|
||||||
"InterlockedWithName": lambda e, p: None,
|
|
||||||
"PartOfAssemblyName": lambda e, p: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.picklists = {
|
|
||||||
"Category-Role": [],
|
|
||||||
"Category-Facility": [],
|
|
||||||
"FloorType": [],
|
|
||||||
"Category-Space": [],
|
|
||||||
"ZoneType": [],
|
|
||||||
"Category-Product": [],
|
|
||||||
"AssetType": [],
|
|
||||||
"DurationUnit": ["day"], # See note about hardcoded day below
|
|
||||||
"Category-Element": [],
|
|
||||||
"SpareType": [],
|
|
||||||
"ApprovalBy": [],
|
|
||||||
"StageType": [],
|
|
||||||
"objType": [],
|
|
||||||
}
|
|
||||||
self.default_date = (datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=-2177452801)).isoformat()
|
|
||||||
|
|
||||||
def parse(self, files):
|
|
||||||
self.files = files
|
|
||||||
for category, get_category in self.categories.items():
|
|
||||||
setattr(self, category, {})
|
|
||||||
get_category()
|
|
||||||
|
|
||||||
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_actor(self, element):
|
|
||||||
psets = ifcopenshell.util.element.get_psets(element)
|
|
||||||
return {
|
|
||||||
"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_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):
|
|
||||||
return result[0]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_facilities(self):
|
|
||||||
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):
|
|
||||||
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):
|
|
||||||
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
|
|
||||||
elevation = element.ObjectPlacement.RelativePlacement.Location.Coordinates[2]
|
|
||||||
self.floors[name] = {
|
|
||||||
"Name": name,
|
|
||||||
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
|
|
||||||
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
|
|
||||||
"Category": "Level",
|
|
||||||
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
|
|
||||||
"ModelObject": element.is_a(),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"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_custom_parameters(self, category, data, element, psets):
|
|
||||||
for parameter, get_parameter in self.custom_parameters.get(category, {}).items():
|
|
||||||
data[parameter] = get_parameter(element, psets)
|
|
||||||
|
|
||||||
def get_spaces(self):
|
|
||||||
for key, ifc in self.files.items():
|
|
||||||
if "arch" not in key:
|
|
||||||
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)
|
|
||||||
data = {
|
|
||||||
"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),
|
|
||||||
}
|
|
||||||
self.get_custom_parameters("spaces", data, element, psets)
|
|
||||||
self.spaces[name] = data
|
|
||||||
|
|
||||||
def get_zones(self):
|
|
||||||
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 or "Unnamed") + (space.Name or "Unnamed")] = {
|
|
||||||
"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():
|
|
||||||
for element in ifc.by_type("IfcSystem"):
|
|
||||||
name = element.Name
|
|
||||||
self.systems[name] = {
|
|
||||||
"Name": name,
|
|
||||||
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
|
|
||||||
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
|
|
||||||
"Category": None,
|
|
||||||
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
"ParentSystemName": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_types(self):
|
|
||||||
for discipline, ifc in self.files.items():
|
|
||||||
self.get_types_from_file(discipline)
|
|
||||||
|
|
||||||
def get_types_from_file(self, ifc_file):
|
|
||||||
primary_keys = []
|
|
||||||
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)
|
|
||||||
data = {
|
|
||||||
"Name": name,
|
|
||||||
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
|
|
||||||
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
|
|
||||||
"Category": self.get_classification(element),
|
|
||||||
"Description": element.Description,
|
|
||||||
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
|
|
||||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
self.get_custom_parameters("types", data, element, psets)
|
|
||||||
self.types[name] = data
|
|
||||||
|
|
||||||
def get_components(self):
|
|
||||||
for discipline, ifc in self.files.items():
|
|
||||||
self.get_components_from_file(discipline)
|
|
||||||
|
|
||||||
def get_components_from_file(self, ifc_file):
|
|
||||||
for element_type in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
|
|
||||||
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
|
|
||||||
for rel in element.HasAssignments:
|
|
||||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a("IfcSystem"):
|
|
||||||
system = rel.RelatingGroup.Name
|
|
||||||
|
|
||||||
space = ifcopenshell.util.element.get_container(element)
|
|
||||||
space_name = space.Name if space.is_a("IfcSpace") else None
|
|
||||||
|
|
||||||
psets = ifcopenshell.util.element.get_psets(element)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"Name": name,
|
|
||||||
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
|
|
||||||
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
|
|
||||||
"TypeName": element_type.Name,
|
|
||||||
"SpaceName": space_name,
|
|
||||||
"SystemName": system,
|
|
||||||
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
|
|
||||||
"ModelObject": "{}[{}]".format(
|
|
||||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element)
|
|
||||||
),
|
|
||||||
"ModelID": element.GlobalId,
|
|
||||||
}
|
|
||||||
self.get_custom_parameters("components", data, element, psets)
|
|
||||||
self.components[name] = data
|
|
||||||
|
|
||||||
def get_documents(self):
|
|
||||||
for ifc in self.files.values():
|
|
||||||
for rel in ifc.by_type("IfcRelAssociatesDocument"):
|
|
||||||
element = rel.RelatingDocument
|
|
||||||
if element.is_a("IfcDocumentInformation"):
|
|
||||||
continue
|
|
||||||
for related_object in rel.RelatedObjects:
|
|
||||||
worksheet_row = related_object.Name
|
|
||||||
if ifc.schema == "IFC2X3":
|
|
||||||
referenced_document = element.ReferenceToDocument[0]
|
|
||||||
identification = referenced_document.ItemReference
|
|
||||||
author_date = None
|
|
||||||
if referenced_document.CreationTime:
|
|
||||||
author_date = ifcopenshell.util.date.ifc2datetime(
|
|
||||||
referenced_document.CreationTime
|
|
||||||
).isoformat()
|
|
||||||
else:
|
|
||||||
referenced_document = element.ReferencedDocument
|
|
||||||
identification = referenced_document.Identification
|
|
||||||
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"
|
|
||||||
|
|
||||||
self.documents[identification + worksheet_name + worksheet_row] = {
|
|
||||||
"Name": identification,
|
|
||||||
"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.Location,
|
|
||||||
"Description": referenced_document.Name,
|
|
||||||
"SpecificationSection": None,
|
|
||||||
"SubmittalID": None,
|
|
||||||
"SourceURL": None,
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# IfcFM - IFC for facility management
|
|
||||||
# Copyright (C) 2021 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 textwrap
|
|
||||||
import ifcopenshell
|
|
||||||
import ifcopenshell.util.fm
|
|
||||||
import ifcopenshell.util.schema
|
|
||||||
import ifcopenshell.util.attribute
|
|
||||||
|
|
||||||
|
|
||||||
def print_element(declaration, should_print_subtypes=True):
|
|
||||||
if declaration.name() in ifcopenshell.util.fm.fmhem_excluded_classes:
|
|
||||||
pass
|
|
||||||
elif declaration.is_abstract():
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
types = []
|
|
||||||
for attribute in declaration.all_attributes():
|
|
||||||
if attribute.name() == "PredefinedType":
|
|
||||||
types = list(ifcopenshell.util.attribute.get_enum_items(attribute))
|
|
||||||
if "NOTDEFINED" in types:
|
|
||||||
types.remove("NOTDEFINED")
|
|
||||||
print("{}".format(declaration.name()))
|
|
||||||
if types:
|
|
||||||
types = sorted(types)
|
|
||||||
for line in textwrap.wrap(", ".join(types), width=70):
|
|
||||||
print("\t\t{}".format(line))
|
|
||||||
if should_print_subtypes:
|
|
||||||
for subtype in declaration.subtypes():
|
|
||||||
print_element(subtype)
|
|
||||||
|
|
||||||
|
|
||||||
def print_fmhem_documentation(schema="IFC4"):
|
|
||||||
if schema == "IFC4":
|
|
||||||
classes = ifcopenshell.util.fm.fmhem_classes_ifc4
|
|
||||||
elif schema == "IFC2X3":
|
|
||||||
classes = ifcopenshell.util.fm.fmhem_classes_ifc2x3
|
|
||||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
|
|
||||||
for ifc_class in classes:
|
|
||||||
try:
|
|
||||||
declaration = schema.declaration_by_name(ifc_class)
|
|
||||||
print_element(declaration)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def print_all_documentation(schema="IFC4"):
|
|
||||||
if schema == "IFC4":
|
|
||||||
classes = ifcopenshell.util.fm.fmhem_classes_ifc4
|
|
||||||
elif schema == "IFC2X3":
|
|
||||||
classes = ifcopenshell.util.fm.fmhem_classes_ifc2x3
|
|
||||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
|
|
||||||
class_queue = [schema.declaration_by_name("IfcElementType")]
|
|
||||||
maintainable_declarations = []
|
|
||||||
other_declarations = []
|
|
||||||
while class_queue:
|
|
||||||
declaration = class_queue.pop(0)
|
|
||||||
class_queue.extend(declaration.subtypes())
|
|
||||||
if declaration.is_abstract():
|
|
||||||
continue
|
|
||||||
is_maintainable = False
|
|
||||||
for ifc_class in classes:
|
|
||||||
if ifcopenshell.util.schema.is_a(declaration, ifc_class):
|
|
||||||
is_maintainable = True
|
|
||||||
break
|
|
||||||
if is_maintainable:
|
|
||||||
maintainable_declarations.append(declaration)
|
|
||||||
else:
|
|
||||||
other_declarations.append(declaration)
|
|
||||||
print("# Maintainable classes\n")
|
|
||||||
for declaration in maintainable_declarations:
|
|
||||||
print_element(declaration, should_print_subtypes=False)
|
|
||||||
print("\n\n# Other classes\n")
|
|
||||||
for declaration in other_declarations:
|
|
||||||
print_element(declaration, should_print_subtypes=False)
|
|
||||||
|
|
||||||
|
|
||||||
print_all_documentation("IFC2X3")
|
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
# IfcFM - IFC for facility management
|
|
||||||
# Copyright (C) 2021 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 csv
|
|
||||||
|
|
||||||
try:
|
|
||||||
from xlsxwriter import Workbook
|
|
||||||
except:
|
|
||||||
pass # No XLSX support
|
|
||||||
|
|
||||||
try:
|
|
||||||
from odf.opendocument import OpenDocumentSpreadsheet
|
|
||||||
from odf.style import Style, TableCellProperties
|
|
||||||
from odf.table import Table, TableRow, TableCell
|
|
||||||
from odf.text import P
|
|
||||||
except:
|
|
||||||
pass # No ODF support
|
|
||||||
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/1143671/how-to-sort-objects-by-multiple-keys-in-python
|
|
||||||
|
|
||||||
from operator import itemgetter as i
|
|
||||||
from functools import cmp_to_key
|
|
||||||
|
|
||||||
|
|
||||||
def cmp(x, y):
|
|
||||||
"""
|
|
||||||
Replacement for built-in function cmp that was removed in Python 3
|
|
||||||
|
|
||||||
Compare the two objects x and y and return an integer according to
|
|
||||||
the outcome. The return value is negative if x < y, zero if x == y
|
|
||||||
and strictly positive if x > y.
|
|
||||||
|
|
||||||
https://portingguide.readthedocs.io/en/latest/comparisons.html#the-cmp-function
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
return (x > y) - (x < y)
|
|
||||||
except:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def multikeysort(items, columns):
|
|
||||||
comparers = [((i(col[1:].strip()), -1) if col.startswith("-") else (i(col.strip()), 1)) for col in columns]
|
|
||||||
|
|
||||||
def comparer(left, right):
|
|
||||||
comparer_iter = (cmp(fn(left), fn(right)) * mult for fn, mult in comparers)
|
|
||||||
return next((result for result in comparer_iter if result), 0)
|
|
||||||
|
|
||||||
return sorted(items, key=cmp_to_key(comparer))
|
|
||||||
|
|
||||||
|
|
||||||
class Writer:
|
|
||||||
def __init__(self, parser, filename=None):
|
|
||||||
self.filename = filename
|
|
||||||
self.parser = parser
|
|
||||||
self.sheet_data = {}
|
|
||||||
self.colours = {
|
|
||||||
"r": "fdff8e", # Required
|
|
||||||
"i": "fdcd94", # Internal reference
|
|
||||||
"e": "cd95ff", # External reference
|
|
||||||
"o": "cdffc8", # Optional
|
|
||||||
"s": "c0c0c0", # Secondary information
|
|
||||||
"p": "9ccaff", # Project specific
|
|
||||||
"n": "000000", # Not used
|
|
||||||
}
|
|
||||||
self.colours = {
|
|
||||||
"r": "dc8774", # Required
|
|
||||||
"i": "eda786", # Internal reference
|
|
||||||
"e": "96c7d0", # External reference
|
|
||||||
"o": "ddb873", # Optional or edd889
|
|
||||||
"s": "dddddd", # Secondary information
|
|
||||||
"p": "b8dd73", # Project specific
|
|
||||||
"n": "000000", # Not used
|
|
||||||
}
|
|
||||||
self.sheets = {
|
|
||||||
"Actor": {
|
|
||||||
"data": self.parser.actors,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"Category",
|
|
||||||
"Email",
|
|
||||||
"Phone",
|
|
||||||
"CompanyURL",
|
|
||||||
"Department",
|
|
||||||
"Address1",
|
|
||||||
"Address2",
|
|
||||||
"StateRegion",
|
|
||||||
"PostalCode",
|
|
||||||
"Country",
|
|
||||||
],
|
|
||||||
"colours": "rirrrrrrrrr",
|
|
||||||
"order": ["Name"],
|
|
||||||
},
|
|
||||||
"Facility": {
|
|
||||||
"data": self.parser.facilities,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"ProjectName",
|
|
||||||
"SiteName",
|
|
||||||
"LinearUnits",
|
|
||||||
"AreaUnits",
|
|
||||||
"AreaMeasurement",
|
|
||||||
"Phase",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelProjectID",
|
|
||||||
"ModelSiteID",
|
|
||||||
"ModelBuildingID",
|
|
||||||
],
|
|
||||||
"colours": "ririrrrrrreeee",
|
|
||||||
"order": ["Name"],
|
|
||||||
},
|
|
||||||
"Floor": {
|
|
||||||
"data": self.parser.floors,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelObject",
|
|
||||||
"ModelID",
|
|
||||||
"Elevation",
|
|
||||||
],
|
|
||||||
"colours": "ririeeer",
|
|
||||||
"order": ["Elevation"],
|
|
||||||
},
|
|
||||||
"Space": {
|
|
||||||
"data": self.parser.spaces,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"LevelName",
|
|
||||||
"Description",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelID",
|
|
||||||
"BuildingRoomNumber",
|
|
||||||
"UsableHeight",
|
|
||||||
"AreaGross",
|
|
||||||
"AreaNet",
|
|
||||||
],
|
|
||||||
"colours": "ririireerrrr",
|
|
||||||
"order": ["LevelName", "Name"],
|
|
||||||
},
|
|
||||||
"Zone": {
|
|
||||||
"data": self.parser.zones,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"SpaceName",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelID",
|
|
||||||
"ParentZoneName",
|
|
||||||
],
|
|
||||||
"colours": "ririieei",
|
|
||||||
"order": ["Name", "SpaceName"],
|
|
||||||
},
|
|
||||||
"Type": {
|
|
||||||
"data": self.parser.types,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"Description",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelObject",
|
|
||||||
"ModelID",
|
|
||||||
],
|
|
||||||
"colours": "ririreee",
|
|
||||||
"order": ["ModelObject", "Name"],
|
|
||||||
},
|
|
||||||
"Component": {
|
|
||||||
"data": self.parser.components,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"TypeName",
|
|
||||||
"SpaceName",
|
|
||||||
"SystemName",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelObject",
|
|
||||||
"ModelID",
|
|
||||||
],
|
|
||||||
"colours": "ririiieee",
|
|
||||||
"order": ["ModelObject", "Name"],
|
|
||||||
},
|
|
||||||
"System": {
|
|
||||||
"data": self.parser.systems,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"ModelSoftware",
|
|
||||||
"ModelID",
|
|
||||||
"ParentSystemName",
|
|
||||||
],
|
|
||||||
"colours": "ririeei",
|
|
||||||
"order": ["Name"],
|
|
||||||
},
|
|
||||||
"Document": {
|
|
||||||
"data": self.parser.documents,
|
|
||||||
"fields": [
|
|
||||||
"Name",
|
|
||||||
"AuthorOrganizationName",
|
|
||||||
"AuthorDate",
|
|
||||||
"Category",
|
|
||||||
"WorksheetName",
|
|
||||||
"WorksheetRow",
|
|
||||||
"Revision",
|
|
||||||
"Location",
|
|
||||||
"Description",
|
|
||||||
"SpecificationSection",
|
|
||||||
"SubmittalID",
|
|
||||||
"SourceURL",
|
|
||||||
],
|
|
||||||
"colours": "ririiirrrrer",
|
|
||||||
"order": ["Name"],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def write(self):
|
|
||||||
for category, spec in self.sheets.items():
|
|
||||||
self.write_data(category, spec["data"], spec["fields"], spec["colours"], spec["order"])
|
|
||||||
|
|
||||||
def write_data(self, sheet, data, fieldnames, colours, sort_fields):
|
|
||||||
self.sheet_data[sheet] = {"headers": fieldnames, "colours": colours, "rows": []}
|
|
||||||
for row in multikeysort(list(data.values()), sort_fields):
|
|
||||||
values = []
|
|
||||||
for fieldname in fieldnames:
|
|
||||||
values.append(row[fieldname])
|
|
||||||
self.sheet_data[sheet]["rows"].append(values)
|
|
||||||
|
|
||||||
|
|
||||||
class CsvWriter(Writer):
|
|
||||||
def write(self):
|
|
||||||
super().write()
|
|
||||||
for sheet, data in self.sheet_data.items():
|
|
||||||
with open(os.path.join(self.filename, "{}.csv".format(sheet)), "w", newline="", encoding="utf-8") as file:
|
|
||||||
writer = csv.writer(file)
|
|
||||||
writer.writerow(data["headers"])
|
|
||||||
for row in data["rows"]:
|
|
||||||
writer.writerow(row)
|
|
||||||
|
|
||||||
|
|
||||||
class XlsWriter(Writer):
|
|
||||||
def write(self):
|
|
||||||
super().write()
|
|
||||||
self.workbook = Workbook(self.filename + ".xlsx")
|
|
||||||
|
|
||||||
self.cell_formats = {}
|
|
||||||
for key, value in self.colours.items():
|
|
||||||
self.cell_formats[key] = self.workbook.add_format()
|
|
||||||
self.cell_formats[key].set_bg_color(value)
|
|
||||||
|
|
||||||
for sheet in self.sheets.keys():
|
|
||||||
self.write_worksheet(sheet)
|
|
||||||
self.workbook.close()
|
|
||||||
|
|
||||||
def write_worksheet(self, name):
|
|
||||||
worksheet = self.workbook.add_worksheet(name)
|
|
||||||
r = 0
|
|
||||||
c = 0
|
|
||||||
for header in self.sheet_data[name]["headers"]:
|
|
||||||
cell = worksheet.write(r, c, header, self.cell_formats["s"])
|
|
||||||
c += 1
|
|
||||||
c = 0
|
|
||||||
r += 1
|
|
||||||
for row in self.sheet_data[name]["rows"]:
|
|
||||||
c = 0
|
|
||||||
for col in row:
|
|
||||||
if c >= len(self.sheet_data[name]["colours"]):
|
|
||||||
cell_format = "p"
|
|
||||||
else:
|
|
||||||
cell_format = self.sheet_data[name]["colours"][c]
|
|
||||||
cell = worksheet.write(r, c, col, self.cell_formats[cell_format])
|
|
||||||
c += 1
|
|
||||||
r += 1
|
|
||||||
|
|
||||||
|
|
||||||
class OdsWriter(Writer):
|
|
||||||
def write(self):
|
|
||||||
super().write()
|
|
||||||
self.doc = OpenDocumentSpreadsheet()
|
|
||||||
|
|
||||||
self.cell_formats = {}
|
|
||||||
for key, value in self.colours.items():
|
|
||||||
style = Style(name=key, family="table-cell")
|
|
||||||
style.addElement(TableCellProperties(backgroundcolor="#" + value))
|
|
||||||
self.doc.automaticstyles.addElement(style)
|
|
||||||
self.cell_formats[key] = style
|
|
||||||
|
|
||||||
for sheet in self.sheets.keys():
|
|
||||||
self.write_table(sheet)
|
|
||||||
self.doc.save(self.filename, True)
|
|
||||||
|
|
||||||
def write_table(self, name):
|
|
||||||
table = Table(name=name)
|
|
||||||
tr = TableRow()
|
|
||||||
for header in self.sheet_data[name]["headers"]:
|
|
||||||
tc = TableCell(valuetype="string", stylename="s")
|
|
||||||
tc.addElement(P(text=header))
|
|
||||||
tr.addElement(tc)
|
|
||||||
table.addElement(tr)
|
|
||||||
for row in self.sheet_data[name]["rows"]:
|
|
||||||
tr = TableRow()
|
|
||||||
c = 0
|
|
||||||
for col in row:
|
|
||||||
if c >= len(self.sheet_data[name]["colours"]):
|
|
||||||
cell_format = "p"
|
|
||||||
else:
|
|
||||||
cell_format = self.sheet_data[name]["colours"][c]
|
|
||||||
tc = TableCell(valuetype="string", stylename=cell_format)
|
|
||||||
if col is None:
|
|
||||||
col = "NULL"
|
|
||||||
tc.addElement(P(text=col))
|
|
||||||
tr.addElement(tc)
|
|
||||||
c += 1
|
|
||||||
table.addElement(tr)
|
|
||||||
self.doc.spreadsheet.addElement(table)
|
|
||||||
Reference in New Issue
Block a user