Compare commits

...

14 Commits

Author SHA1 Message Date
bimgeek 650122e041 final touches (hopefully) 2025-08-19 20:20:15 +03:00
bimgeek 7c6e7c52c1 early return ifc quantities 2025-08-19 20:16:45 +03:00
bimgeek 6430e81995 function call elimination 2025-08-19 20:12:22 +03:00
bimgeek dc0eb24d9c move unit mapping to module level 2025-08-19 20:08:22 +03:00
bimgeek 484f31dbfa take only what you need 2025-08-19 19:33:25 +03:00
bimgeek 661c7c70a8 simplify get quantities 2025-08-18 22:30:49 +03:00
bimgeek 061ddf33fd cache by field name 2025-08-18 22:24:12 +03:00
bimgeek 94a2d86900 comm cleanup 2025-08-18 22:05:59 +03:00
bimgeek 8e44310f91 quantity extraction py 2025-08-18 22:04:17 +03:00
bimgeek a56085b1b4 quantity field unit cache 2025-08-18 21:55:30 +03:00
bimgeek 4f20315582 module cache for project units 2025-08-18 21:45:30 +03:00
bimgeek 452e764b6a add units first pass 2025-08-18 21:14:02 +03:00
bimgeek 86ac9a2b91 cleanup 2025-08-18 17:20:55 +03:00
bimgeek af39a52f42 add qtos 2025-08-18 17:10:12 +03:00
3 changed files with 223 additions and 0 deletions
+7
View File
@@ -3,6 +3,8 @@ from typing import Any
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.util.element import get_type
from speckleifc.qtos_only import get_quantities
def extract_properties(element: entity_instance) -> dict[str, object]:
properties: dict[str, object] = {
@@ -10,6 +12,11 @@ def extract_properties(element: entity_instance) -> dict[str, object]:
"Property Sets": _get_ifc_object_properties(element),
}
# Add quantities if they exist
quantities = get_quantities(element)
if quantities:
properties["Quantities"] = quantities
if (ifc_type := get_type(element)) is not None:
properties["Element Type Property Sets"] = _get_ifc_element_type_properties(
ifc_type,
+106
View File
@@ -0,0 +1,106 @@
from typing import Any
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.util.unit import get_full_unit_name, get_project_unit
# Module-level constants for units
UNIT_MAPPING = {
"IfcQuantityLength": "LENGTHUNIT",
"IfcQuantityArea": "AREAUNIT",
"IfcQuantityVolume": "VOLUMEUNIT",
"IfcQuantityCount": None, # Count quantities have no units
"IfcQuantityWeight": "MASSUNIT",
"IfcQuantityTime": "TIMEUNIT"
}
def _get_unit_info(element: entity_instance, quantity) -> dict[str, str]:
"""Get unit information for a quantity."""
try:
# Early return for count quantities - they don't have units
quantity_type = quantity.is_a()
if quantity_type == "IfcQuantityCount":
return {}
if quantity.Unit is not None:
# Quantity has its own unit
try:
unit_name = get_full_unit_name(quantity.Unit)
formatted_unit_name = unit_name.replace("_", " ").title() if unit_name else ""
return {"units": formatted_unit_name}
except:
return {"units": str(quantity.Unit)}
else:
# Fall back to project unit based on quantity type
unit_type = UNIT_MAPPING.get(quantity_type)
if not unit_type:
return {}
# Get the project unit for this unit type
project_unit = get_project_unit(element.file, unit_type, use_cache=True)
if not project_unit:
return {}
# Get unit name and format
unit_name = get_full_unit_name(project_unit)
formatted_unit_name = unit_name.replace("_", " ").title() if unit_name else ""
return {"units": formatted_unit_name}
except Exception:
# If anything fails, return empty dict
return {}
def _get_quantities(quantities: list[entity_instance], element: entity_instance) -> dict[str, Any]:
"""Extract quantity values from IfcPhysicalQuantity entities."""
results = {}
for quantity in quantities or []:
quantity_name = quantity.Name
quantity_type = quantity.is_a() # Cache the type check
if quantity_type == "IfcPhysicalSimpleQuantity":
# Get the quantity value (3rd attribute for simple quantities)
value = getattr(quantity, quantity.attribute_name(3))
unit_info = _get_unit_info(element, quantity)
if unit_info:
# Create structured quantity object with units
results[quantity_name] = {
"name": quantity_name,
"value": value,
**unit_info,
}
else:
# No unit info available, keep as simple value with name
results[quantity_name] = {"name": quantity_name, "value": value}
elif quantity_type == "IfcPhysicalComplexQuantity":
# Handle complex quantities
data = {k: v for k, v in quantity.get_info().items() if v is not None and k != "Name"}
data["properties"] = _get_quantities(quantity.HasQuantities, element)
del data["HasQuantities"]
results[quantity_name] = data
return results
def get_quantities(element: entity_instance) -> dict[str, object]:
"""
Extract quantity takeoffs (QTOs) from an IFC element with unit information.
"""
qtos = {}
# Handle elements with IsDefinedBy relationship
if element.IsDefinedBy:
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
definition = relationship.RelatingPropertyDefinition
if definition.is_a("IfcElementQuantity"):
try:
quantities_data = _get_quantities(definition.Quantities, element)
quantities_data["id"] = definition.id()
qtos[definition.Name] = quantities_data
except (KeyError, AttributeError):
# If entity access fails, skip this quantity set
continue
return qtos
+110
View File
@@ -0,0 +1,110 @@
from typing import Any
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.util.element import get_psets
from ifcopenshell.util.unit import get_full_unit_name, get_project_unit
def _format_unit_name(unit_name: str) -> str:
"""
Convert IFC unit names to user-friendly format.
"""
if not unit_name:
return ""
# Convert underscore-separated words to space-separated and title case
return unit_name.replace("_", " ").title()
def _get_unit_info(element: entity_instance, quantity_type: str) -> dict[str, str]:
"""
Get unit information for a given quantity type from the IFC project.
"""
try:
# Map IFC quantity types to unit types
unit_type_mapping = {
"IfcQuantityLength": "LENGTHUNIT",
"IfcQuantityArea": "AREAUNIT",
"IfcQuantityVolume": "VOLUMEUNIT",
"IfcQuantityCount": None, # Count quantities typically have no units
"IfcQuantityWeight": "MASSUNIT",
"IfcQuantityTime": "TIMEUNIT",
}
unit_type = unit_type_mapping.get(quantity_type)
if not unit_type:
return {}
# Get the project unit for this unit type (with built-in caching)
project_unit = get_project_unit(element.file, unit_type, use_cache=True)
if not project_unit:
return {}
# Get unit name
unit_name = get_full_unit_name(project_unit)
# Format the unit name to be user-friendly
formatted_unit_name = _format_unit_name(unit_name)
return {"units": formatted_unit_name}
except Exception:
# If anything fails, return empty dict to maintain robustness
return {}
def get_quantities(element: entity_instance) -> dict[str, object]:
"""
Extract quantity takeoffs (QTOs) from an IFC element with unit information.
"""
# Get basic quantities using existing utility
quantities = get_psets(element, qtos_only=True, should_inherit=False)
if not quantities:
return {}
# Enhance each QTO pset with unit information
enhanced_quantities = {}
for pset_name, pset_data in quantities.items():
if not isinstance(pset_data, dict) or "id" not in pset_data:
# Fallback for unexpected data structure
enhanced_quantities[pset_name] = pset_data
continue
try:
# Get the actual IfcElementQuantity entity
pset_entity = element.file.by_id(pset_data["id"])
# Transform quantities to include unit information
enhanced_pset = {"id": pset_data["id"]}
# Create mapping of quantity names to their IFC entities for unit lookup
quantity_entities = {
q.Name: q for q in pset_entity.Quantities if hasattr(q, "Name")
}
for qty_name, qty_value in pset_data.items():
if qty_name == "id":
continue
# Get the IFC quantity entity for unit information
qty_entity = quantity_entities[qty_name]
unit_info = _get_unit_info(element, qty_entity.is_a())
if unit_info:
# Create structured quantity object with units
enhanced_pset[qty_name] = {
"name": qty_name,
"value": qty_value,
**unit_info,
}
else:
# No unit info available, keep as simple value with name
enhanced_pset[qty_name] = {"name": qty_name, "value": qty_value}
enhanced_quantities[pset_name] = enhanced_pset
except (KeyError, AttributeError):
# If entity access fails, use original data as fallback
enhanced_quantities[pset_name] = pset_data
return enhanced_quantities