From 308e1bb59d2298dc302c0c5bfa85daff1136c3f1 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:06:15 +0100 Subject: [PATCH] Feat(prop): Added better property extraction (#8) * Added better property extraction * property sets naming --- src/speckleifc/__main__.py | 2 - .../converter/data_object_converter.py | 3 +- .../converter/geometry_converter.py | 18 ++-- src/speckleifc/converter/project_converter.py | 3 +- .../converter/spatial_element_converter.py | 6 -- src/speckleifc/property_extraction.py | 99 +++++++++++++------ 6 files changed, 79 insertions(+), 52 deletions(-) diff --git a/src/speckleifc/__main__.py b/src/speckleifc/__main__.py index bb2a6b6..9e4ee57 100644 --- a/src/speckleifc/__main__.py +++ b/src/speckleifc/__main__.py @@ -16,7 +16,6 @@ from speckleifc.importer import ImportJob def cmd_line_import() -> None: - parser = ArgumentParser( prog="speckleifc", description="imports a file", @@ -76,7 +75,6 @@ def open_and_convert_file( model_id: str, account: Account, ) -> Version: - start = time.time() very_start = start diff --git a/src/speckleifc/converter/data_object_converter.py b/src/speckleifc/converter/data_object_converter.py index 13d922d..2e2edb0 100644 --- a/src/speckleifc/converter/data_object_converter.py +++ b/src/speckleifc/converter/data_object_converter.py @@ -24,6 +24,5 @@ def data_object_to_speckle( data_object["@elements"] = children data_object["ifcType"] = step_element.is_a() - data_object["expressId"] = step_element.id() - data_object["description"] = cast(str | None, step_element.Description) + return data_object diff --git a/src/speckleifc/converter/geometry_converter.py b/src/speckleifc/converter/geometry_converter.py index 84edef3..28289ae 100644 --- a/src/speckleifc/converter/geometry_converter.py +++ b/src/speckleifc/converter/geometry_converter.py @@ -70,15 +70,15 @@ def geometry_to_speckle( i += 1 face_index += 3 # number of items in the faces list we just jumped over - mapped_index_counters[ - mesh_index - ] += 3 # number of verts we just added to the mesh.vertices i.e. the next index - mapped_faces_pointers[ - mesh_index - ] += 4 # number of item's we've just added to the mesh.faces list - mapped_vertices_pointers[ - mesh_index - ] += 9 # number of item's we've just added to the mesh.vertices list + mapped_index_counters[mesh_index] += ( + 3 # number of verts we just added to the mesh.vertices i.e. the next index + ) + mapped_faces_pointers[mesh_index] += ( + 4 # number of item's we've just added to the mesh.faces list + ) + mapped_vertices_pointers[mesh_index] += ( + 9 # number of item's we've just added to the mesh.vertices list + ) return mapped_meshes # type: ignore diff --git a/src/speckleifc/converter/project_converter.py b/src/speckleifc/converter/project_converter.py index 8d31c69..ec272d0 100644 --- a/src/speckleifc/converter/project_converter.py +++ b/src/speckleifc/converter/project_converter.py @@ -13,9 +13,8 @@ def project_to_speckle( project = Collection(applicationId=guid, name=name, elements=children) - project["expressId"] = step_element.id() project["ifcType"] = step_element.is_a() - project["description"] = cast(str | None, step_element.Description) + project["description"] = step_element.Description project["objectType"] = step_element.ObjectType project["longName"] = step_element.LongName project["phase"] = step_element.Phase diff --git a/src/speckleifc/converter/spatial_element_converter.py b/src/speckleifc/converter/spatial_element_converter.py index 036278a..fc042eb 100644 --- a/src/speckleifc/converter/spatial_element_converter.py +++ b/src/speckleifc/converter/spatial_element_converter.py @@ -20,7 +20,6 @@ def spatial_element_to_speckle( name = cast(str, step_element.Name or step_element.LongName or guid) data_object = Collection(applicationId=guid, name=name, elements=all_children) - data_object["expressId"] = step_element.id() data_object["ifcType"] = step_element.is_a() return data_object @@ -38,11 +37,6 @@ def _convert_as_data_object( displayValue=display_value, ) - data_object["expressId"] = step_element.id() data_object["ifcType"] = step_element.is_a() - data_object["description"] = cast(str | None, step_element.Description) - data_object["objectType"] = step_element.ObjectType - data_object["compositionType"] = step_element.CompositionType - data_object["longName"] = step_element.LongName return data_object diff --git a/src/speckleifc/property_extraction.py b/src/speckleifc/property_extraction.py index b13e5cb..4d848a1 100644 --- a/src/speckleifc/property_extraction.py +++ b/src/speckleifc/property_extraction.py @@ -1,50 +1,87 @@ from typing import Any from ifcopenshell.entity_instance import entity_instance +from ifcopenshell.util.element import get_type def extract_properties(element: entity_instance) -> dict[str, object]: + properties: dict[str, object] = { + "Attributes": get_attributes(element), + "Property Sets": _get_ifc_object_properties(element), + } + + if (ifc_type := get_type(element)) is not None: + properties["Element Type Property Sets"] = _get_ifc_element_type_properties( + ifc_type, + ) + + return properties + + +def get_attributes(element: entity_instance) -> dict[str, object]: + return element.get_info(True, False, scalar_only=True) + + +def _get_ifc_element_type_properties(element: entity_instance) -> dict[str, object]: + result: dict[str, object] = {} + for definition in element.HasPropertySets or []: + if not definition.is_a("IfcPropertySet"): + continue + + result[definition.Name] = _get_properties(definition.HasProperties) + return result + + +def _get_ifc_object_properties(element: entity_instance) -> dict[str, object]: result: dict[str, object] = {} for rel in getattr(element, "IsDefinedBy", []): if not rel.is_a("IfcRelDefinesByProperties"): continue - prop_set = rel.RelatingPropertyDefinition - if not prop_set.is_a("IfcPropertySet"): + definition: entity_instance = rel.RelatingPropertyDefinition + + if not definition.is_a("IfcPropertySet"): continue - set_name = prop_set.Name - properties: dict[str, Any] = {} - - for prop in prop_set.HasProperties: - name = prop.Name - - if prop.is_a("IfcPropertySingleValue"): - val = prop.NominalValue - if val is not None: - properties[name] = ( - val.wrappedValue if hasattr(val, "wrappedValue") else val - ) - elif prop.is_a("IfcPropertyListValue"): - values = getattr(prop, "ListValues", None) - if values: - properties[name] = [ - v.wrappedValue if hasattr(v, "wrappedValue") else v - for v in values - ] - elif prop.is_a("IfcPropertyEnumeratedValue"): - values = getattr(prop, "EnumerationValues", None) - if values: - properties[name] = [ - v.wrappedValue if hasattr(v, "wrappedValue") else v - for v in values - ] - - # elif prop.is_a("IfcPropertyTableValue"): - # properties[name] = #not sure if we want to support these... + set_name = definition.Name + properties = _get_properties(definition.HasProperties) if properties: result[set_name] = properties return result + + +def _get_properties(properties: entity_instance) -> dict[str, Any]: + """ + There already exists a canonical way to get properties + `ifcopenshell.util.element.get_properties` but it's very verbose + and we don't want to bloat our selves with supporting complex property types + + This is a slimmed down version, only supporting a couple of property types + """ + result: dict[str, Any] = {} + + for prop in properties: + name = prop.Name + if prop.is_a("IfcPropertySingleValue"): + val = prop.NominalValue + if val is not None: + result[name] = val.wrappedValue if hasattr(val, "wrappedValue") else val + elif prop.is_a("IfcPropertyListValue"): + values = getattr(prop, "ListValues", None) + if values: + result[name] = [ + v.wrappedValue if hasattr(v, "wrappedValue") else v for v in values + ] + elif prop.is_a("IfcPropertyEnumeratedValue"): + values = getattr(prop, "EnumerationValues", None) + if values: + result[name] = [ + v.wrappedValue if hasattr(v, "wrappedValue") else v for v in values + ] + + # elif prop.is_a("IfcPropertyTableValue"): + # properties[name] = #not sure if we want to support these... + return result