From 5c0a7af830f0177f530176f81727b311f49190d3 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 13 Jan 2025 13:22:26 +0500 Subject: [PATCH] cobie24 - implement Coordinate tab #5926 --- src/ifcfm/ifcfm/__init__.py | 6 +- src/ifcfm/ifcfm/cobie24.py | 109 ++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/ifcfm/ifcfm/__init__.py b/src/ifcfm/ifcfm/__init__.py index e6e6a628f6..10741bb798 100644 --- a/src/ifcfm/ifcfm/__init__.py +++ b/src/ifcfm/ifcfm/__init__.py @@ -49,6 +49,7 @@ __version__ = version = "0.0.0" ParserPreset = Literal["basic", "cobie24", "cobie24legacy"] +GetElementDataCallBack = Callable[[ifcopenshell.file, ifcopenshell.entity_instance], dict[str, Any]] _parser_presets_configs = {} @@ -69,9 +70,7 @@ def get_presets_configs() -> dict[ParserPreset, dict[str, Any]]: class Parser: 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]] - ] + get_custom_element_data: dict[str, Union[GetElementDataCallBack, dict[str, Any]]] duplicate_keys: list[tuple[dict[str, Any], dict[str, Any]]] def __init__(self, preset: Union[str, ParserPreset, dict[str, Any]] = "basic"): @@ -93,6 +92,7 @@ class Parser: def parse(self, ifc_file: ifcopenshell.file, name=None): for category_name, category_config in self.config["categories"].items(): for element in category_config["get_category_elements"](ifc_file): + get_element_data: Union[GetElementDataCallBack, dict[str, Any]] get_element_data = category_config["get_element_data"] if isinstance(get_element_data, dict): diff --git a/src/ifcfm/ifcfm/cobie24.py b/src/ifcfm/ifcfm/cobie24.py index 44edcc4360..1d5451de3b 100644 --- a/src/ifcfm/ifcfm/cobie24.py +++ b/src/ifcfm/ifcfm/cobie24.py @@ -17,13 +17,16 @@ # along with IfcFM. If not, see . import ifcopenshell +import ifcopenshell.geom import ifcopenshell.util.classification import ifcopenshell.util.date import ifcopenshell.util.element import ifcopenshell.util.fm import ifcopenshell.util.placement +import ifcopenshell.util.shape import ifcopenshell.util.system -from typing import Any, Union, Optional +from ifcopenshell.util.shape_builder import np_matrix_to_euler +from typing import Any, Union, Optional, Generator # The original BIMServer plugin has a function called ifcToCOBie: @@ -163,7 +166,7 @@ def get_documents( def get_attributes(ifc_file: ifcopenshell.file) -> list[dict[str, Any]]: - results = [] + results: list[dict[str, Any]] = [] history = get_history(ifc_file) created_by = get_email_from_history(history) if history else None created_on = ifcopenshell.util.date.ifc2datetime(history.CreationDate).isoformat() if history else None @@ -850,12 +853,81 @@ def get_document_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_ } -def get_attribute_data( - ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance -) -> ifcopenshell.entity_instance: +def pass_category_elements_as_data(ifc_file: ifcopenshell.file, element: dict[str, Any]) -> dict[str, Any]: return element +def get_coordinate_elements(ifc_file: ifcopenshell.file) -> list[dict[str, Any]]: + elements = get_floors(ifc_file) + get_spaces(ifc_file) + results: list[dict[str, Any]] = [] + for element in elements: + for data in get_coordinate_data_(element): + results.append(data) + return results + + +def get_coordinate_data_(element: ifcopenshell.entity_instance) -> Generator[dict[str, Any], None, None]: + M_TRANSLATION = (slice(0, 3), 3) + element_name = val(element.Name) + element_class = element.is_a() + + base_data = { + "CreatedBy": get_created_by(element), + "CreatedOn": get_created_on(element), + "RowName": element_name, + "ExtSystem": "IfcFm", + "ExtObject": element_class, + "ExtIdentifier": element.GlobalId, + } + + if element_class == "IfcBuildingStorey": + matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) + categories = ("points",) + rotation = np_matrix_to_euler(matrix) + translation = matrix[M_TRANSLATION] + # Rotation axes as explained in https://www.nibs.org/nbims/v3/cobie. + points_data = { + "Name": element_name, + "Category": "point", + "SheetName": "Floor", + "CoordinateXAxis": translation[0], + "CoordinateYAxis": translation[1], + "CoordinateZAxis": translation[2], + "ClockwiseRotation": rotation[2] * 180, + "ElevationalRotation": rotation[0] * 180, + "YawRotation": rotation[1] * 180, + } + yield base_data | points_data + return + + # IfcSpaces. + if element.Representation is None: + return + + settings = ifcopenshell.geom.settings() + shape: ifcopenshell.geom.ShapeElementType + shape = ifcopenshell.geom.create_shape(settings, element) + verts = ifcopenshell.util.shape.get_shape_vertices(shape, shape.geometry) + categories = ("box-lowerleft", "box-upperright") + bbox = ifcopenshell.util.shape.get_bbox(verts) + base_data = base_data | { + "Category": "point", + "SheetName": "Space", + # Spaces don't need rotation as box points are already in world space. + "ClockwiseRotation": None, + "ElevationalRotation": None, + "YawRotation": None, + } + for category, point in zip(categories, bbox): + box_point_data = { + "Name": element_name + category, + "CoordinateXAxis": point[0], + "CoordinateYAxis": point[1], + "CoordinateZAxis": point[2], + } + yield base_data | box_point_data + + 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: @@ -936,6 +1008,7 @@ def get_facility_parent( def val(x: Any) -> Any: + """Replace n/a and empty strings with `None`.""" return x if x not in ("", "n/a") else None @@ -1428,7 +1501,31 @@ config = { "colours": "ririiirreeeoo", "sort": [{"name": "Category", "order": "ASC"}, {"name": "Name", "order": "ASC"}], "get_category_elements": get_attributes, - "get_element_data": get_attribute_data, + "get_element_data": pass_category_elements_as_data, + }, + "Coordinate": { + "keys": ["Name", "SheetName", "RowName"], + "headers": ( + "Name", + "CreatedBy", + "CreatedOn", + "Category", + "SheetName", + "RowName", + "CoordinateXAxis", + "CoordinateYAxis", + "CoordinateZAxis", + "ExtSystem", + "ExtObject", + "ExtIdentifier", + "ClockwiseRotation", + "ElevationalRotation", + "YawRotation", + ), + "colours": "ririiirrreeerrr", + "sort": [{"name": "Category", "order": "ASC"}, {"name": "Name", "order": "ASC"}], + "get_category_elements": get_coordinate_elements, + "get_element_data": pass_category_elements_as_data, }, }, }