This commit is contained in:
Andrej730
2024-11-06 11:47:17 +05:00
parent 4e070545ae
commit 4fdb313449
9 changed files with 232 additions and 155 deletions
+1 -1
View File
@@ -171,7 +171,7 @@ class BIM_OT_multiple_file_selector(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@classmethod
def poll(self, context):
def poll(cls, context):
return getattr(context, "file_props", None) is not None
def execute(self, context):
+1
View File
@@ -434,6 +434,7 @@ class BIM_PT_tabs(Panel):
"QUALITY",
"SWITCH",
]:
# Draw a little underscore below the active tab icon.
if aprops.tab == tab:
row.prop(aprops, "active_tab", text="", icon="BLANK1")
else:
+38 -18
View File
@@ -20,6 +20,9 @@ import os
import re
import csv
import importlib
from pathlib import Path
from collections import defaultdict
from typing import Literal, Union, Any, Callable
try:
from openpyxl import Workbook
@@ -44,12 +47,21 @@ except:
__version__ = version = "0.0.0"
ParserPreset = Literal["basic", "cobie24", "cobie24legacy"]
class Parser:
def __init__(self, preset="basic"):
config: dict[str, Any]
categories: defaultdict[str, dict[str, Any]]
get_custom_element_data: dict[
str, Union[Callable[[ifcopenshell.file, ifcopenshell.entity_instance], dict[str, Any]], dict[str, Any]]
]
duplicate_keys: list[tuple[dict[str, Any], dict[str, Any]]]
def __init__(self, preset: Union[ParserPreset, dict[str, Any]] = "basic"):
self.file = None
self.preset = preset
self.categories = {}
self.config = None
self.categories = defaultdict(dict)
self.get_custom_element_data = {}
self.duplicate_keys = []
@@ -59,9 +71,9 @@ class Parser:
else:
self.config = preset
def parse(self, ifc_file, name=None):
# TODO: name is unused?
def parse(self, ifc_file: ifcopenshell.file, name=None):
for category_name, category_config in self.config["categories"].items():
self.categories.setdefault(category_name, {})
for element in category_config["get_category_elements"](ifc_file):
get_element_data = category_config["get_element_data"]
@@ -69,7 +81,7 @@ class Parser:
data = {}
for key, query in get_element_data.items():
data[key] = ifcopenshell.util.selector.get_element_value(element, query)
else:
elif isinstance(get_element_data, Callable):
data = get_element_data(ifc_file, element) or {}
get_custom_element_data = self.get_custom_element_data.get(category_name, lambda x, y: None)
@@ -77,28 +89,26 @@ class Parser:
custom_data = {}
for key, query in get_custom_element_data.items():
custom_data[key] = ifcopenshell.util.selector.get_element_value(element, query)
else:
elif isinstance(get_custom_element_data, Callable):
custom_data = get_custom_element_data(ifc_file, element) or {}
data.update(custom_data)
if data:
key = "-".join([str(data[k]) for k in category_config["keys"]])
# TODO: duplicate_keys are never used?
if key in self.categories[category_name]:
self.duplicate_keys.append((self.categories[category_name][key], data))
self.categories[category_name][key] = data
def federate(self, paths):
def federate(self, paths: list[str]) -> None:
for path in paths:
spreadsheet = pd.ExcelFile(path)
sheet_names = spreadsheet.sheet_names
for category_name, category_config in self.config["categories"].items():
self.categories.setdefault(category_name, {})
if category_name not in sheet_names:
continue
self.categories.setdefault(category_name, {})
df = pd.read_excel(spreadsheet, sheet_name=category_name, keep_default_na=False)
for _, row in df.iterrows():
key = "-".join([str(row[k]) for k in category_config["keys"]])
@@ -106,18 +116,21 @@ class Parser:
continue
self.categories[category_name][key] = row.to_dict()
def exclude_categories(self, names):
def exclude_categories(self, names: list[str]) -> None:
for name in names:
if name in self.config["categories"]:
del self.config["categories"][name]
def exclude_element_data(self, category, names):
def exclude_element_data(self, category: str, names: list[str]) -> None:
headers = self.config["categories"][category]["headers"]
self.config["categories"][category]["headers"] = [h for h in headers if h not in names]
class Writer:
def __init__(self, parser):
config: dict[str, Any]
categories: dict[str, dict[str, Any]]
def __init__(self, parser: Parser):
self.parser = parser
if isinstance(self.parser.preset, str):
module = importlib.import_module(f"ifcfm.{self.parser.preset}")
@@ -127,7 +140,14 @@ class Writer:
else:
self.config = getattr(self.parser.preset, "config")
def write(self, null="N/A", empty="-", bool_true="YES", bool_false="NO", list_separator=", "):
def write(
self,
null: str = "N/A",
empty: str = "-",
bool_true: str = "YES",
bool_false: str = "NO",
list_separator: str = ", ",
) -> None:
self.categories = {}
null = self.config.get("null", null)
empty = self.config.get("empty", empty)
@@ -191,7 +211,7 @@ class Writer:
for row in data["rows"]:
writer.writerow(row)
def write_ods(self, output):
def write_ods(self, output: str) -> None:
doc = OpenDocumentSpreadsheet()
for key, value in self.config.get("colours", {}).items():
@@ -229,7 +249,7 @@ class Writer:
doc.save(output, True)
def write_xlsx(self, output):
def write_xlsx(self, output: str) -> None:
workbook = Workbook()
cell_formats = {}
@@ -270,7 +290,7 @@ class Writer:
workbook.save(output)
def write_pd(self):
def write_pd(self) -> dict[str, pd.DataFrame]:
results = {}
for category, data in self.categories.items():
results[category] = pd.DataFrame(data["rows"], columns=data["headers"])
+18
View File
@@ -1,3 +1,21 @@
# 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 ifcfm
import argparse
import ifcopenshell
+31 -25
View File
@@ -17,26 +17,28 @@
# 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
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.fm
import ifcopenshell.util.placement
import ifcopenshell.util.system
from typing import Any, Union, Optional
def get_facilities(ifc_file):
def get_facilities(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcBuilding")
def get_storeys(ifc_file):
def get_storeys(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcBuildingStorey")
def get_spaces(ifc_file):
def get_spaces(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
def get_zones(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
zones = []
for zone in ifc_file.by_type("IfcZone"):
for rel in zone.IsGroupedBy:
@@ -44,22 +46,22 @@ def get_zones(ifc_file):
return zones
def get_element_types(ifc_file):
def get_element_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
def get_elements(ifc_file):
def get_elements(ifc_file: ifcopenshell.file) -> set[ifcopenshell.entity_instance]:
elements = set()
for element_type in get_element_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
def get_systems(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcSystem")
def get_facility_data(ifc_file, element):
def get_facility_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
return {
"Name": element.Name,
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
@@ -78,7 +80,7 @@ def get_facility_data(ifc_file, element):
}
def get_storey_data(ifc_file, element):
def get_storey_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
return {
"Name": element.Name,
"ClassificationIdentification": "Level",
@@ -92,7 +94,7 @@ def get_storey_data(ifc_file, element):
}
def get_space_data(ifc_file, element):
def get_space_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
psets = ifcopenshell.util.element.get_psets(element)
return {
"Name": element.Name,
@@ -110,7 +112,7 @@ def get_space_data(ifc_file, element):
}
def get_zone_data(ifc_file, element):
def get_zone_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
zone, space = element
return {
"Name": zone.Name,
@@ -122,7 +124,7 @@ def get_zone_data(ifc_file, element):
}
def get_element_type_data(ifc_file, element):
def get_element_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
psets = ifcopenshell.util.element.get_psets(element)
return {
"Name": element.Name,
@@ -143,7 +145,7 @@ def get_element_type_data(ifc_file, element):
}
def get_element_data(ifc_file, element):
def get_element_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
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)
@@ -170,7 +172,7 @@ def get_element_data(ifc_file, element):
}
def get_system_data(ifc_file, element):
def get_system_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
return {
"Name": element.Name,
"Description": element.Description,
@@ -183,25 +185,27 @@ def get_system_data(ifc_file, element):
}
def get_owner_name(element):
def get_owner_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_owner_creation_date(element):
def get_owner_creation_date(element: ifcopenshell.entity_instance) -> Union[str, None]:
if not getattr(element, "OwnerHistory", None):
return
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
def get_owner_application(element):
def get_owner_application(element: ifcopenshell.entity_instance) -> Union[str, None]:
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningApplication.ApplicationFullName
def get_facility_parent(element, ifc_class):
def get_facility_parent(
element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
@@ -211,7 +215,7 @@ def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(parent)
def get_classification_identification(element):
def get_classification_identification(element: ifcopenshell.entity_instance) -> Union[str, None]:
references = list(ifcopenshell.util.classification.get_references(element))
if references:
if hasattr(references[0], "Identification"):
@@ -219,13 +223,15 @@ def get_classification_identification(element):
return references[0].ItemReference
def get_classification_name(element):
def get_classification_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
references = list(ifcopenshell.util.classification.get_references(element))
if references:
return references[0].Name
def get_property(psets, pset_name, prop_name, decimals=None):
def get_property(
psets: dict[str, Any], pset_name: str, prop_name: str, decimals: Optional[int] = None
) -> Union[Any, None, float]:
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
+67 -52
View File
@@ -17,11 +17,13 @@
# 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
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.fm
import ifcopenshell.util.placement
import ifcopenshell.util.system
from typing import Any, Union, Optional
# The original BIMServer plugin has a function called ifcToCOBie:
@@ -34,15 +36,15 @@ import ifcopenshell.util.classification
# Impact, Coordinate, Issue, Picklist
def get_contacts(ifc_file):
def get_contacts(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcActor")
def get_facilities(ifc_file):
def get_facilities(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcBuilding")
def get_floors(ifc_file):
def get_floors(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return [
e
for e in ifc_file.by_type("IfcBuildingStorey")
@@ -50,11 +52,11 @@ def get_floors(ifc_file):
]
def get_spaces(ifc_file):
def get_spaces(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
def get_zones(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
zones = ifc_file.by_type("IfcZone")
for zone in zones or []:
@@ -69,18 +71,18 @@ def get_zones(ifc_file):
return results
def get_types(ifc_file):
def get_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.fm.get_cobie_types(ifc_file)
def get_components(ifc_file):
def get_components(ifc_file: ifcopenshell.file) -> set[ifcopenshell.entity_instance]:
elements = set()
for element_type in get_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
def get_systems(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
components = get_components(ifc_file)
if ifc_file.schema == "IFC2X3":
@@ -94,7 +96,7 @@ def get_systems(ifc_file):
return results
def get_assemblies(ifc_file):
def get_assemblies(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
layer_sets = ifc_file.by_type("IfcMaterialLayerSet")
layer_sets = [] # This is temporarily overridden because it is unclear exactly how this is stored in Type.
@@ -128,23 +130,25 @@ def get_assemblies(ifc_file):
return results
def get_connections(ifc_file):
def get_connections(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcRelConnectsPorts")
def get_spares(ifc_file):
def get_spares(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcConstructionProductResource")
def get_resources(ifc_file):
def get_resources(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcConstructionEquipmentResource")
def get_jobs(ifc_file):
def get_jobs(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcTask")
def get_documents(ifc_file):
def get_documents(
ifc_file: ifcopenshell.file,
) -> list[tuple[ifcopenshell.entity_instance, ifcopenshell.entity_instance, ifcopenshell.entity_instance]]:
# The original COBie-Plugins assumes a single related object per rel. I think this was wrong.
results = []
for rel in ifc_file.by_type("IfcRelAssociatesDocument"):
@@ -158,7 +162,7 @@ def get_documents(ifc_file):
return results
def get_attributes(ifc_file):
def get_attributes(ifc_file: ifcopenshell.file) -> list[dict[str, Any]]:
results = []
history = get_history(ifc_file)
created_by = get_email_from_history(history) if history else None
@@ -247,7 +251,7 @@ def get_attributes(ifc_file):
return results
def get_contact_data(ifc_file, element):
def get_contact_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
the_actor = element.TheActor
if the_actor.is_a("IfcPerson"):
@@ -299,7 +303,7 @@ def get_contact_data(ifc_file, element):
}
def get_facility_data(ifc_file, element):
def get_facility_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
site = get_facility_parent(element, "IfcSite")
site_name = None
site_description = None
@@ -343,7 +347,7 @@ def get_facility_data(ifc_file, element):
}
def get_floor_data(ifc_file, element):
def get_floor_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
height_names = {
"Height",
"NetHeight",
@@ -382,7 +386,7 @@ def get_floor_data(ifc_file, element):
}
def get_space_data(ifc_file, element):
def get_space_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
floor_name = None
floor = ifcopenshell.util.element.get_aggregate(element)
if floor and floor.is_a("IfcBuildingStorey"):
@@ -425,7 +429,7 @@ def get_space_data(ifc_file, element):
}
def get_zone_data(ifc_file, element):
def get_zone_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
zone, space = element
name = zone.Name
@@ -449,7 +453,7 @@ def get_zone_data(ifc_file, element):
}
def get_type_data(ifc_file, element):
def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
asset_type = None
manufacturer = None
model_number = None
@@ -555,7 +559,7 @@ def get_type_data(ifc_file, element):
}
def get_component_data(ifc_file, element):
def get_component_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
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)
@@ -610,7 +614,7 @@ def get_component_data(ifc_file, element):
}
def get_system_data(ifc_file, element):
def get_system_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
system, component = element
category = get_category(system)
component_name = val(component.Name)
@@ -627,7 +631,7 @@ def get_system_data(ifc_file, element):
}
def get_assembly_data(ifc_file, element):
def get_assembly_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
rel, relating_object, related_object = element
if relating_object.is_a("IfcMaterialLayerSet"):
@@ -663,7 +667,7 @@ def get_assembly_data(ifc_file, element):
}
def get_connection_data(ifc_file, element):
def get_connection_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
connection_type = (
val(element.RelatingPort.ObjectType)
or val(element.RelatedPort.ObjectType)
@@ -691,7 +695,7 @@ def get_connection_data(ifc_file, element):
}
def get_spare_data(ifc_file, element):
def get_spare_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
type_name = None
for rel in element.ResourceOf or []:
for related_object in rel.RelatedObjects or []:
@@ -726,7 +730,7 @@ def get_spare_data(ifc_file, element):
}
def get_resource_data(ifc_file, element):
def get_resource_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
return {
"Name": val(element.Name),
"CreatedBy": get_created_by(element),
@@ -739,7 +743,7 @@ def get_resource_data(ifc_file, element):
}
def get_job_data(ifc_file, element):
def get_job_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
type_names = []
resource_names = []
for rel in element.OperatesOn or []:
@@ -810,7 +814,7 @@ def get_job_data(ifc_file, element):
}
def get_document_data(ifc_file, element):
def get_document_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
rel, doc, related_object = element
directory = getattr(doc, "Location", None)
file = None
@@ -847,11 +851,13 @@ def get_document_data(ifc_file, element):
}
def get_attribute_data(ifc_file, element):
def get_attribute_data(
ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
return element
def get_unit_type_name(ifc_file, unit_type):
def get_unit_type_name(ifc_file: ifcopenshell.file, unit_type: str) -> Union[str, None]:
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"):
@@ -868,17 +874,17 @@ def get_unit_type_name(ifc_file, unit_type):
return val(unit.Currency)
def get_unit_name(ifc_file, unit):
def get_unit_name(ifc_file: ifcopenshell.entity_instance, unit: ifcopenshell.entity_instance) -> Union[str, None]:
if unit.is_a("IfcNamedUnit"):
return val(unit.Name)
def get_created_by(element):
def get_created_by(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return get_email_from_history(element.OwnerHistory)
def get_email_from_history(element):
def get_email_from_history(element: ifcopenshell.entity_instance) -> Union[str, None]:
pao = element.OwningUser
if pao.is_a("IfcPersonAndOrganization"):
return get_email_from_pao(pao.ThePerson, pao.TheOrganization)
@@ -888,7 +894,9 @@ def get_email_from_history(element):
return get_email_from_pao(None, pao)
def get_email_from_pao(person, organization):
def get_email_from_pao(
person: ifcopenshell.entity_instance, organization: ifcopenshell.entity_instance
) -> Union[str, None]:
if organization:
for address in organization.Addresses or []:
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
@@ -900,23 +908,25 @@ def get_email_from_pao(person, organization):
return address.ElectronicMailAddresses[0]
def get_owner_name(element):
def get_owner_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_created_on(element):
def get_created_on(element: ifcopenshell.entity_instance) -> str:
if getattr(element, "OwnerHistory", None):
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
return "1900-12-31T23:59:59" # Yes, really
def get_external_system(element):
def get_external_system(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return val(element.OwnerHistory.OwningApplication.ApplicationFullName)
def get_facility_parent(element, ifc_class):
def get_facility_parent(
element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
@@ -926,11 +936,11 @@ def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(parent)
def val(x):
def val(x: Any) -> Any:
return x if x not in ("", "n/a") else None
def get_area_measurement(element):
def get_area_measurement(element: ifcopenshell.entity_instance) -> Union[str, None]:
for relationship in getattr(element, "IsDefinedBy", []) or []:
if relationship.is_a("IfcRelDefinesByProperties"):
definition = relationship.RelatingPropertyDefinition
@@ -950,7 +960,7 @@ def get_area_measurement(element):
return value
def get_category(element):
def get_category(element: ifcopenshell.entity_instance) -> Union[str, None]:
references = list(ifcopenshell.util.classification.get_references(element))
results = []
for reference in references:
@@ -968,7 +978,9 @@ def get_category(element):
return ",".join(results)
def get_pao_address(person, organization, name):
def get_pao_address(
person: ifcopenshell.entity_instance, organization: ifcopenshell.entity_instance, name: str
) -> Union[str, None]:
for actor in [organization, person]:
if not actor:
continue
@@ -982,7 +994,9 @@ def get_pao_address(person, organization, name):
return result
def get_property(psets, pset_name, prop_name, decimals=None):
def get_property(
psets: dict[str, dict[str, Any]], pset_name: str, prop_name: str, decimals: Optional[int] = None
) -> Union[Any, None, float]:
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
@@ -990,13 +1004,13 @@ def get_property(psets, pset_name, prop_name, decimals=None):
return round(result, decimals)
def get_history(ifc_file):
def get_history(ifc_file: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
histories = ifc_file.by_type("IfcOwnerHistory")
if histories:
return sorted(histories, key=lambda x: x.id())[-1]
def get_property_unit(pset, prop_name):
def get_property_unit(pset: ifcopenshell.entity_instance, prop_name: str) -> Union[str, None]:
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
unit = getattr(prop, "Unit", None)
@@ -1004,7 +1018,8 @@ def get_property_unit(pset, prop_name):
return get_unit_name(unit)
def get_allowed_values(pset_id, prop_name):
# TODO: never used?
def get_allowed_values(ifc_file: ifcopenshell.file, pset_id: int, prop_name: str) -> Union[str, None]:
pset = ifc_file.by_id(pset_id)
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
@@ -1012,7 +1027,7 @@ def get_allowed_values(pset_id, prop_name):
return ",".join([v.wrappedValue for v in prop.EnumerationValues])
def get_sheet_name(element):
def get_sheet_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
if element.is_a("IfcBuilding"):
return "Facility"
elif element.is_a("IfcBuildingStorey"):
+63 -52
View File
@@ -18,11 +18,13 @@
import ifcopenshell
import ifcopenshell.guid
import ifcopenshell.util.fm
import ifcopenshell.util.date
import ifcopenshell.util.system
import ifcopenshell.util.placement
import ifcopenshell.util.classification
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.fm
import ifcopenshell.util.placement
import ifcopenshell.util.system
from typing import Any, Union, Optional
# The original BIMServer plugin has a function called ifcToCOBie:
@@ -35,15 +37,15 @@ import ifcopenshell.util.classification
# Impact, Coordinate, Issue, Picklist
def get_contacts(ifc_file):
def get_contacts(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcPersonAndOrganization")
def get_facilities(ifc_file):
def get_facilities(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcBuilding")
def get_floors(ifc_file):
def get_floors(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return [
e
for e in ifc_file.by_type("IfcBuildingStorey")
@@ -51,11 +53,11 @@ def get_floors(ifc_file):
]
def get_spaces(ifc_file):
def get_spaces(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
def get_zones(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
zones = ifc_file.by_type("IfcZone")
for zone in zones or []:
@@ -86,18 +88,18 @@ def get_zones(ifc_file):
return results
def get_types(ifc_file):
def get_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.fm.get_cobie_types(ifc_file)
def get_components(ifc_file):
def get_components(ifc_file: ifcopenshell.file) -> set[ifcopenshell.entity_instance]:
elements = set()
for element_type in get_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
def get_systems(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
components = get_components(ifc_file)
if ifc_file.schema == "IFC2X3":
@@ -111,7 +113,7 @@ def get_systems(ifc_file):
return results
def get_assemblies(ifc_file):
def get_assemblies(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
results = []
layer_sets = ifc_file.by_type("IfcMaterialLayerSet")
layer_sets = [] # This is temporarily overridden because it is unclear exactly how this is stored in Type.
@@ -145,23 +147,25 @@ def get_assemblies(ifc_file):
return results
def get_connections(ifc_file):
def get_connections(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcRelConnectsPorts")
def get_spares(ifc_file):
def get_spares(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcConstructionProductResource")
def get_resources(ifc_file):
def get_resources(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcConstructionEquipmentResource")
def get_jobs(ifc_file):
def get_jobs(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
return ifc_file.by_type("IfcTask")
def get_documents(ifc_file):
def get_documents(
ifc_file: ifcopenshell.file,
) -> list[tuple[ifcopenshell.entity_instance, ifcopenshell.entity_instance, ifcopenshell.entity_instance]]:
# The original COBie-Plugins assumes a single related object per rel. I think this was wrong.
results = []
for rel in ifc_file.by_type("IfcRelAssociatesDocument"):
@@ -175,7 +179,7 @@ def get_documents(ifc_file):
return results
def get_attributes(ifc_file):
def get_attributes(ifc_file: ifcopenshell.file) -> list[dict[str, Any]]:
results = []
history = get_history(ifc_file)
created_by = get_email_from_history(history) if history else None
@@ -265,7 +269,7 @@ def get_attributes(ifc_file):
return results
def get_contact_data(ifc_file, element):
def get_contact_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
email = get_email_from_pao(element)
history = get_history(ifc_file)
@@ -314,7 +318,7 @@ def get_contact_data(ifc_file, element):
}
def get_facility_data(ifc_file, element):
def get_facility_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
site = get_facility_parent(element, "IfcSite")
site_name = None
site_description = None
@@ -365,7 +369,7 @@ def get_facility_data(ifc_file, element):
}
def get_floor_data(ifc_file, element):
def get_floor_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
external_object = element.is_a()
if element.ObjectType and element.ObjectType.lower() in ("site", "ifcsite"):
external_object = "IfcSite"
@@ -409,7 +413,7 @@ def get_floor_data(ifc_file, element):
}
def get_space_data(ifc_file, element):
def get_space_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
floor_name = None
floor = ifcopenshell.util.element.get_aggregate(element)
if floor and floor.is_a("IfcBuildingStorey"):
@@ -452,7 +456,7 @@ def get_space_data(ifc_file, element):
}
def get_zone_data(ifc_file, element):
def get_zone_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
zone, space = element
if isinstance(zone, tuple):
@@ -493,7 +497,7 @@ def get_zone_data(ifc_file, element):
}
def get_type_data(ifc_file, element):
def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
pset_metadata = {}
pset_mapping = {
"manufacturer": {"Manufacturer"},
@@ -628,7 +632,7 @@ def get_type_data(ifc_file, element):
}
def get_component_data(ifc_file, element):
def get_component_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
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)
@@ -684,7 +688,7 @@ def get_component_data(ifc_file, element):
}
def get_system_data(ifc_file, element):
def get_system_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
system, component = element
category = get_category(system)
component_name = val(component.Name)
@@ -702,7 +706,7 @@ def get_system_data(ifc_file, element):
}
def get_assembly_data(ifc_file, element):
def get_assembly_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
rel, relating_object, related_object = element
if relating_object.is_a("IfcMaterialLayerSet"):
@@ -739,7 +743,7 @@ def get_assembly_data(ifc_file, element):
}
def get_connection_data(ifc_file, element):
def get_connection_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
connection_type = (
val(element.RelatingPort.ObjectType)
or val(element.RelatedPort.ObjectType)
@@ -768,7 +772,7 @@ def get_connection_data(ifc_file, element):
}
def get_spare_data(ifc_file, element):
def get_spare_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
type_name = None
for rel in element.ResourceOf or []:
for related_object in rel.RelatedObjects or []:
@@ -804,7 +808,7 @@ def get_spare_data(ifc_file, element):
}
def get_resource_data(ifc_file, element):
def get_resource_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
return {
"key": val(element.Name),
"Name": val(element.Name),
@@ -818,7 +822,7 @@ def get_resource_data(ifc_file, element):
}
def get_job_data(ifc_file, element):
def get_job_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
type_names = []
resource_names = []
for rel in element.OperatesOn or []:
@@ -890,7 +894,7 @@ def get_job_data(ifc_file, element):
}
def get_document_data(ifc_file, element):
def get_document_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
rel, doc, related_object = element
directory = getattr(doc, "Location", None)
file = None
@@ -928,11 +932,13 @@ def get_document_data(ifc_file, element):
}
def get_attribute_data(ifc_file, element):
def get_attribute_data(
ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
return element
def get_unit_type_name(ifc_file, unit_type):
def get_unit_type_name(ifc_file: ifcopenshell.file, unit_type: str) -> Union[str, None]:
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"):
@@ -949,23 +955,23 @@ def get_unit_type_name(ifc_file, unit_type):
return val(unit.Currency)
def get_unit_name(ifc_file, unit):
def get_unit_name(ifc_file: ifcopenshell.entity_instance, unit: ifcopenshell.entity_instance) -> Union[str, None]:
if unit.is_a("IfcNamedUnit"):
return val(unit.Name)
def get_created_by(element):
def get_created_by(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return get_email_from_history(element.OwnerHistory)
def get_email_from_history(element):
def get_email_from_history(element: ifcopenshell.entity_instance) -> Union[str, None]:
pao = element.OwningUser
if pao.is_a("IfcPersonAndOrganization"):
return get_email_from_pao(pao)
def get_email_from_pao(pao):
def get_email_from_pao(pao: ifcopenshell.entity_instance) -> Union[str, None]:
for address in pao.ThePerson.Addresses or []:
if address.is_a("IfcTelecomAddress") and address.ElectronicMailAddresses:
return address.ElectronicMailAddresses[0]
@@ -986,23 +992,25 @@ def get_email_from_pao(pao):
return pao.ThePerson.GivenName + pao.ThePerson.FamilyName + "@" + pao.TheOrganization.Name + ".com"
def get_owner_name(element):
def get_owner_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_created_on(element):
def get_created_on(element: ifcopenshell.entity_instance) -> str:
if getattr(element, "OwnerHistory", None):
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
return "1900-12-31T23:59:59" # Yes, really
def get_external_system(element):
def get_external_system(element: ifcopenshell.entity_instance) -> Union[str, None]:
if getattr(element, "OwnerHistory", None):
return val(element.OwnerHistory.OwningApplication.ApplicationFullName)
def get_facility_parent(element, ifc_class):
def get_facility_parent(
element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
@@ -1012,11 +1020,11 @@ def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(parent)
def val(x):
def val(x: Any) -> Any:
return x if x not in ("", "n/a") else None
def get_area_measurement(element):
def get_area_measurement(element: ifcopenshell.entity_instance) -> Union[str, None]:
for relationship in getattr(element, "IsDefinedBy", []) or []:
if relationship.is_a("IfcRelDefinesByProperties"):
definition = relationship.RelatingPropertyDefinition
@@ -1029,7 +1037,7 @@ def get_area_measurement(element):
return result
def get_category(element):
def get_category(element: ifcopenshell.entity_instance) -> Union[str, None]:
references = list(ifcopenshell.util.classification.get_references(element))
results = []
for reference in references:
@@ -1078,7 +1086,7 @@ def get_category(element):
return val(getattr(element, "ObjectType", None))
def get_pao_address(element, name):
def get_pao_address(element: ifcopenshell.entity_instance, name: str) -> Union[str, None]:
for actor in [element.ThePerson, element.TheOrganization]:
for address in actor.Addresses or []:
if hasattr(address, name) and getattr(address, name, None):
@@ -1090,7 +1098,9 @@ def get_pao_address(element, name):
return result
def get_property(psets, pset_name, prop_name, decimals=None):
def get_property(
psets: dict[str, dict[str, Any]], pset_name: str, prop_name: str, decimals: Optional[int] = None
) -> Union[Any, None, float]:
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
@@ -1098,13 +1108,13 @@ def get_property(psets, pset_name, prop_name, decimals=None):
return round(result, decimals)
def get_history(ifc_file):
def get_history(ifc_file: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
histories = ifc_file.by_type("IfcOwnerHistory")
if histories:
return sorted(histories, key=lambda x: x.id())[-1]
def get_property_unit(pset, prop_name):
def get_property_unit(pset: ifcopenshell.entity_instance, prop_name: str) -> Union[str, None]:
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
unit = getattr(prop, "Unit", None)
@@ -1112,7 +1122,8 @@ def get_property_unit(pset, prop_name):
return get_unit_name(unit)
def get_allowed_values(pset_id, prop_name):
# TODO: never used?
def get_allowed_values(ifc_file: ifcopenshell.file, pset_id: int, prop_name: str) -> Union[str, None]:
pset = ifc_file.by_id(pset_id)
for prop in getattr(pset, "HasProperties", []) or []:
if prop.Name == prop_name:
@@ -1120,7 +1131,7 @@ def get_allowed_values(pset_id, prop_name):
return ",".join([v.wrappedValue for v in prop.EnumerationValues])
def get_sheet_name(element):
def get_sheet_name(element: ifcopenshell.entity_instance) -> Union[str, None]:
if element.is_a("IfcBuilding"):
return "Facility"
elif element.is_a("IfcBuildingStorey"):
@@ -19,6 +19,7 @@
import ifcopenshell
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
from typing import Any, Callable, Optional, Union, Literal, overload
from collections import namedtuple
@@ -17,7 +17,10 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.attribute
from typing import Literal
from typing_extensions import assert_never
# COBie actually uses an exclusion list, but this inclusion list is equivalent.
cobie_type_classes = [
@@ -114,7 +117,7 @@ fmhem_excluded_classes = [
]
def get_cobie_types(ifc_file):
def get_cobie_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
elements = []
for ifc_class in cobie_type_classes:
try:
@@ -124,7 +127,7 @@ def get_cobie_types(ifc_file):
return elements
def get_cobie_components(ifc_file):
def get_cobie_components(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
elements = []
for ifc_class in cobie_component_classes:
try:
@@ -134,7 +137,7 @@ def get_cobie_components(ifc_file):
return elements
def get_fmhem_types(ifc_file):
def get_fmhem_types(ifc_file: ifcopenshell.file) -> list[ifcopenshell.entity_instance]:
elements = []
if ifc_file.schema == "IFC2X3":
fmhem_classes = fmhem_classes_ifc2x3
@@ -148,10 +151,10 @@ def get_fmhem_types(ifc_file):
return elements
def get_fmhem_classes(schema="IFC4"):
def get_fmhem_classes(schema: Literal["IFC4", "IFC2X3"] = "IFC4") -> dict[str, list[str]]:
results = {}
def get_fmhem_class(declaration):
def get_fmhem_class(declaration: W.entity) -> None:
if declaration.name() in fmhem_excluded_classes:
pass
elif declaration.is_abstract():
@@ -172,8 +175,10 @@ def get_fmhem_classes(schema="IFC4"):
classes = fmhem_classes_ifc4
elif schema == "IFC2X3":
classes = fmhem_classes_ifc2x3
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
else:
assert_never(schema)
schema_ = ifcopenshell.schema_by_name(schema)
for ifc_class in classes:
declaration = schema.declaration_by_name(ifc_class)
declaration = schema_.declaration_by_name(ifc_class)
get_fmhem_class(declaration)
return results