take only what you need

This commit is contained in:
bimgeek
2025-08-19 19:33:25 +03:00
parent 661c7c70a8
commit 484f31dbfa
3 changed files with 117 additions and 65 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Any
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.util.element import get_type
from speckleifc.quantity_extraction import get_quantities
from speckleifc.qtos_only import get_quantities
def extract_properties(element: entity_instance) -> dict[str, object]:
+112
View File
@@ -0,0 +1,112 @@
from typing import Any
from ifcopenshell.entity_instance import entity_instance
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) -> dict[str, str]:
"""Get unit information for a quantity."""
try:
if hasattr(quantity, 'Unit') and quantity.Unit:
# Quantity has its own unit
try:
unit_name = get_full_unit_name(quantity.Unit)
formatted_unit_name = _format_unit_name(unit_name)
return {"units": formatted_unit_name}
except:
return {"units": str(quantity.Unit)}
else:
# Fall back to project unit based on quantity type
unit_mapping = {
"IfcQuantityLength": "LENGTHUNIT",
"IfcQuantityArea": "AREAUNIT",
"IfcQuantityVolume": "VOLUMEUNIT",
"IfcQuantityCount": None, # Count quantities typically have no units
"IfcQuantityWeight": "MASSUNIT",
"IfcQuantityTime": "TIMEUNIT"
}
quantity_type = quantity.is_a()
unit_type = unit_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(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
if quantity.is_a("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.is_a("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 hasattr(element, "IsDefinedBy") and 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
+4 -64
View File
@@ -4,66 +4,6 @@ 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
# Global cache for project units per IFC file
_file_project_units_cache: dict[int, dict[str, Any]] = {}
# Cache for unit information by field name per file
_quantity_field_units_cache: dict[int, dict[str, dict[str, str]]] = {}
def _get_cached_project_unit(element: entity_instance, unit_type: str):
"""
Get project unit with caching per file.
"""
file_id = id(element.file) # Use file object ID as cache key
# Initialize cache for this file if needed
if file_id not in _file_project_units_cache:
_file_project_units_cache[file_id] = {}
file_cache = _file_project_units_cache[file_id]
# Check if we already cached this unit type for this file
if unit_type in file_cache:
return file_cache[unit_type]
# Not cached - get project unit and cache it
try:
project_unit = get_project_unit(element.file, unit_type)
file_cache[unit_type] = project_unit
return project_unit
except Exception:
# Cache None for failed lookups to avoid repeated failures
file_cache[unit_type] = None
return None
def _get_cached_field_unit_info(element: entity_instance, qty_entity) -> dict[str, str]:
"""
Get unit info for quantity field with caching by field name.
"""
file_id = id(element.file)
field_name = qty_entity.Name
# Handle empty field names with fallback to direct computation
if not field_name:
return _get_unit_info(element, qty_entity.is_a())
# Initialize file cache if needed
if file_id not in _quantity_field_units_cache:
_quantity_field_units_cache[file_id] = {}
field_cache = _quantity_field_units_cache[file_id]
# Check if we already cached this field name for this file
if field_name in field_cache:
return field_cache[field_name]
# Not cached - compute unit info and cache it by field name
unit_info = _get_unit_info(element, qty_entity.is_a())
field_cache[field_name] = unit_info
return unit_info
def _format_unit_name(unit_name: str) -> str:
"""
@@ -95,8 +35,8 @@ def _get_unit_info(element: entity_instance, quantity_type: str) -> dict[str, st
if not unit_type:
return {}
# Get the project unit for this unit type (cached)
project_unit = _get_cached_project_unit(element, unit_type)
# 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 {}
@@ -117,7 +57,7 @@ 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)
quantities = get_psets(element, qtos_only=True, should_inherit=False)
if not quantities:
return {}
@@ -148,7 +88,7 @@ def get_quantities(element: entity_instance) -> dict[str, object]:
# Get the IFC quantity entity for unit information
qty_entity = quantity_entities[qty_name]
unit_info = _get_cached_field_unit_info(element, qty_entity)
unit_info = _get_unit_info(element, qty_entity.is_a())
if unit_info:
# Create structured quantity object with units