This commit is contained in:
Andrej730
2025-02-04 17:37:48 +05:00
parent bd2c3a0ca1
commit ecd7282150
@@ -23,28 +23,29 @@ from logging import Logger
from collections import defaultdict from collections import defaultdict
from typing import List, Dict, Tuple, Optional from typing import List, Dict, Tuple, Optional
class Patcher: class Patcher:
""" """
Assigns fractions to material constituents based on their "width". Assigns fractions to material constituents based on their "width".
In Reference View MVD, material layer information is exchanged through In Reference View MVD, material layer information is exchanged through
IfcMaterialConstituentSet and IfcPhysicalComplexQuantity instead of IfcMaterialLayerSet. IfcMaterialConstituentSet and IfcPhysicalComplexQuantity instead of IfcMaterialLayerSet.
The layer thicknesses are stored as IfcQuantityLength within IfcPhysicalComplexQuantity The layer thicknesses are stored as IfcQuantityLength within IfcPhysicalComplexQuantity
with a Discrimination of 'layer'. with a Discrimination of 'layer'.
While the Fraction attribute in IfcMaterialConstituent is optional, it's valuable for While the Fraction attribute in IfcMaterialConstituent is optional, it's valuable for
downstream applications as it indicates the relative proportion of each constituent in downstream applications as it indicates the relative proportion of each constituent in
the total material composition in a more straightforward way. the total material composition in a more straightforward way.
However, authoring applications often export material constituents without setting this attribute. However, authoring applications often export material constituents without setting this attribute.
This patcher helps maintain proper material information in Reference View exports by: This patcher helps maintain proper material information in Reference View exports by:
1. Finding width quantities from IfcPhysicalComplexQuantity with 'layer' discrimination 1. Finding width quantities from IfcPhysicalComplexQuantity with 'layer' discrimination
2. Using these widths to calculate relative proportions 2. Using these widths to calculate relative proportions
3. Setting the optional but valuable Fraction attribute 3. Setting the optional but valuable Fraction attribute
For example, if a wall has constituents with widths of 0.1m and 0.2m, their fractions For example, if a wall has constituents with widths of 0.1m and 0.2m, their fractions
would be set to 0.333 and 0.667 respectively. would be set to 0.333 and 0.667 respectively.
References: References:
- IFC4 Reference View MVD: https://standards.buildingsmart.org/MVD/RELEASE/IFC4/ADD2_TC1/RV1_2/HTML/ - IFC4 Reference View MVD: https://standards.buildingsmart.org/MVD/RELEASE/IFC4/ADD2_TC1/RV1_2/HTML/
- IfcMaterialConstituent: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmaterialresource/lexical/ifcmaterialconstituent.htm - IfcMaterialConstituent: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmaterialresource/lexical/ifcmaterialconstituent.htm
@@ -57,11 +58,11 @@ class Patcher:
def patch(self): def patch(self):
"""Execute the patch to assign fractions to material constituents.""" """Execute the patch to assign fractions to material constituents."""
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
# Get length unit from project # Get length unit from project
length_unit = ifcopenshell.util.unit.get_project_unit(self.file, "LENGTHUNIT") length_unit = ifcopenshell.util.unit.get_project_unit(self.file, "LENGTHUNIT")
for constituent_set in self.file.by_type('IfcMaterialConstituentSet'): for constituent_set in self.file.by_type("IfcMaterialConstituentSet"):
if not (constituents := constituent_set.MaterialConstituents): if not (constituents := constituent_set.MaterialConstituents):
continue continue
@@ -81,9 +82,7 @@ class Patcher:
continue continue
# Calculate constituent widths and total width # Calculate constituent widths and total width
constituent_widths, total_width = self.calculate_constituent_widths( constituent_widths, total_width = self.calculate_constituent_widths(constituents, elements, unit_scale)
constituents, elements, unit_scale
)
if not constituent_widths: if not constituent_widths:
continue continue
@@ -100,25 +99,31 @@ class Patcher:
def get_element_quantities(self, element: ifcopenshell.entity_instance) -> Dict[str, float]: def get_element_quantities(self, element: ifcopenshell.entity_instance) -> Dict[str, float]:
"""Get width quantities for an element.""" """Get width quantities for an element."""
qtos = [v for k,v in ifcopenshell.util.element.get_psets(element, qtos_only=True).items() qtos = [
if k.endswith("BaseQuantities")] v
for k, v in ifcopenshell.util.element.get_psets(element, qtos_only=True).items()
if k.endswith("BaseQuantities")
]
if qtos: if qtos:
return {k:v["properties"]["Width"] for k,v in qtos[0].items() return {
if isinstance(v,dict) and k: v["properties"]["Width"]
(v.get("Discrimination") or "").lower() == "layer" and for k, v in qtos[0].items()
v.get("properties",{}).get("Width", "") is not None} if isinstance(v, dict)
and (v.get("Discrimination") or "").lower() == "layer"
and v.get("properties", {}).get("Width", "") is not None
}
return {} return {}
def calculate_constituent_widths( def calculate_constituent_widths(
self, self,
constituents: List[ifcopenshell.entity_instance], constituents: List[ifcopenshell.entity_instance],
elements: set[ifcopenshell.entity_instance], elements: set[ifcopenshell.entity_instance],
unit_scale: float unit_scale: float,
) -> Tuple[Dict[ifcopenshell.entity_instance, float], float]: ) -> Tuple[Dict[ifcopenshell.entity_instance, float], float]:
"""Calculate the widths of constituents based on associated quantities.""" """Calculate the widths of constituents based on associated quantities."""
if not elements: if not elements:
return {}, 0.0 return {}, 0.0
# Get quantities from all elements and use the first valid one # Get quantities from all elements and use the first valid one
element_quantities = {} element_quantities = {}
for element in elements: for element in elements:
@@ -127,18 +132,18 @@ class Patcher:
element_quantities = quantities element_quantities = quantities
self.logger.debug(f"Using quantities from element: {element.GlobalId}") self.logger.debug(f"Using quantities from element: {element.GlobalId}")
break break
if not element_quantities: if not element_quantities:
self.logger.warning("No valid quantities found in any element") self.logger.warning("No valid quantities found in any element")
return {}, 0.0 return {}, 0.0
constituent_widths = {} constituent_widths = {}
total_width = 0.0 total_width = 0.0
for constituent in constituents: for constituent in constituents:
if not constituent.Name: # Skip unnamed constituents as per RV MVD if not constituent.Name: # Skip unnamed constituents as per RV MVD
continue continue
constituent_name = constituent.Name.strip() constituent_name = constituent.Name.strip()
if width := element_quantities.get(constituent_name): if width := element_quantities.get(constituent_name):
constituent_widths[constituent] = width * unit_scale constituent_widths[constituent] = width * unit_scale