Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 650122e041 | |||
| 7c6e7c52c1 | |||
| 6430e81995 | |||
| dc0eb24d9c | |||
| 484f31dbfa | |||
| 661c7c70a8 | |||
| 061ddf33fd | |||
| 94a2d86900 | |||
| 8e44310f91 | |||
| a56085b1b4 | |||
| 4f20315582 | |||
| 452e764b6a | |||
| 86ac9a2b91 | |||
| af39a52f42 |
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user