|
21 | 21 |
|
22 | 22 | logger = logging.getLogger(__name__) |
23 | 23 | quality_logger = logging.getLogger('bim2sim.QualityReport') |
24 | | -settings_products = ifcopenshell.geom.main.settings() |
| 24 | +settings_products = ifcopenshell.geom.settings() |
25 | 25 | settings_products.set(settings_products.USE_PYTHON_OPENCASCADE, True) |
| 26 | +settings_products.set(settings_products.USE_WORLD_COORDS, True) |
| 27 | +settings_products.set(settings_products.PRECISION, 1e-6) |
26 | 28 |
|
27 | 29 |
|
28 | 30 | class ElementError(Exception): |
@@ -55,6 +57,7 @@ def __init__(self, guid=None, **kwargs): |
55 | 57 | self.guid = guid or self.get_id(self.guid_prefix) |
56 | 58 | # self.related_decisions: List[Decision] = [] |
57 | 59 | self.attributes = attribute.AttributeManager(bind=self) |
| 60 | + self.element_type = self.__class__.__name__ |
58 | 61 |
|
59 | 62 | # set attributes based on kwargs |
60 | 63 | for kw, arg in kwargs.items(): |
@@ -160,9 +163,9 @@ class IFCBased(Element): |
160 | 163 | '-Something' start with minus to exclude |
161 | 164 |
|
162 | 165 | For example: |
163 | | - {'IfcSlab': ['*', '-SomethingSpecialWeDontWant', 'BASESLAB']} |
164 | | - {'IfcRoof': ['FLAT_ROOF', 'SHED_ROOF',...], |
165 | | - 'IfcSlab': ['ROOF']}""" |
| 166 | + >>> {'IfcSlab': ['*', '-SomethingSpecialWeDontWant', 'BASESLAB']} |
| 167 | + >>> {'IfcRoof': ['FLAT_ROOF', 'SHED_ROOF',...], |
| 168 | + >>> 'IfcSlab': ['ROOF']}""" |
166 | 169 |
|
167 | 170 | ifc_types: Dict[str, List[str]] = None |
168 | 171 | pattern_ifc_type = [] |
@@ -333,29 +336,47 @@ def filter_properties(self, patterns): |
333 | 336 | return matches |
334 | 337 |
|
335 | 338 | @classmethod |
336 | | - def filter_for_text_fragments( |
337 | | - cls, ifc_element, ifc_units: dict, optional_locations: list = None): |
338 | | - """Filter for text fragments in the ifc_element to identify the ifc_element.""" |
| 339 | + def filter_for_text_fragments(cls, ifc_element, ifc_units: dict, |
| 340 | + optional_locations: list = None): |
| 341 | + """Find text fragments that match the class patterns in an IFC element. |
| 342 | +
|
| 343 | + Args: |
| 344 | + ifc_element: The IFC element to check. |
| 345 | + ifc_units: Dictionary containing IFC unit information. |
| 346 | + optional_locations: Additional locations to check patterns beyond |
| 347 | + name. Defaults to None. |
| 348 | +
|
| 349 | + Returns: |
| 350 | + list: List of matched fragments, empty list if no matches found. |
| 351 | + """ |
339 | 352 | results = [] |
340 | | - hits = [p.search(ifc_element.Name) for p in cls.pattern_ifc_type] |
341 | | - # hits.extend([p.search(ifc_element.Description or '') for p in cls.pattern_ifc_type]) |
342 | | - hits = [x for x in hits if x is not None] |
343 | | - if any(hits): |
344 | | - quality_logger.info("Identified %s through text fracments in name. Criteria: %s", cls.ifc_type, hits) |
345 | | - results.append(hits[0][0]) |
346 | | - # return hits[0][0] |
| 353 | + |
| 354 | + # Check name matches |
| 355 | + name_hits = [p.search(ifc_element.Name) for p in cls.pattern_ifc_type] |
| 356 | + name_hits = [hit for hit in name_hits if hit is not None] |
| 357 | + if name_hits: |
| 358 | + quality_logger.info( |
| 359 | + f"Identified {cls.ifc_type} through text fragments in name. " |
| 360 | + f"Criteria: {name_hits}") |
| 361 | + results.append(name_hits[0][0]) |
| 362 | + |
| 363 | + # Check optional locations |
347 | 364 | if optional_locations: |
348 | 365 | for loc in optional_locations: |
349 | | - hits = [p.search(ifc2python.get_property_set_by_name( |
350 | | - loc, ifc_element, ifc_units) or '') |
351 | | - for p in cls.pattern_ifc_type |
352 | | - if ifc2python.get_property_set_by_name( |
353 | | - loc, ifc_element, ifc_units)] |
354 | | - hits = [x for x in hits if x is not None] |
355 | | - if any(hits): |
356 | | - quality_logger.info("Identified %s through text fracments in %s. Criteria: %s", cls.ifc_type, loc, hits) |
357 | | - results.append(hits[0][0]) |
358 | | - return results if results else '' |
| 366 | + prop_value = ifc2python.get_property_set_by_name( |
| 367 | + loc, ifc_element, ifc_units) |
| 368 | + if not prop_value: |
| 369 | + continue |
| 370 | + |
| 371 | + loc_hits = [p.search(prop_value) for p in cls.pattern_ifc_type] |
| 372 | + loc_hits = [hit for hit in loc_hits if hit is not None] |
| 373 | + if loc_hits: |
| 374 | + quality_logger.info( |
| 375 | + f"Identified {cls.ifc_type} through text fragments " |
| 376 | + f"in {loc}. Criteria: {loc_hits}") |
| 377 | + results.append(loc_hits[0][0]) |
| 378 | + |
| 379 | + return results |
359 | 380 |
|
360 | 381 | def get_exact_property(self, propertyset_name: str, property_name: str): |
361 | 382 | """Returns value of property specified by propertyset name and property name |
@@ -526,6 +547,17 @@ def calc_cost_group(self) -> Optional[int]: |
526 | 547 | """Calculate the cost group according to DIN276""" |
527 | 548 | return None |
528 | 549 |
|
| 550 | + def calc_product_shape(self): |
| 551 | + """Calculate the product shape based on IfcProduct representation.""" |
| 552 | + if hasattr(self.ifc, 'Representation'): |
| 553 | + try: |
| 554 | + shape = ifcopenshell.geom.create_shape( |
| 555 | + settings_products, self.ifc).geometry |
| 556 | + return shape |
| 557 | + except: |
| 558 | + logger.warning(f"No calculation of product shape possible " |
| 559 | + f"for {self.ifc}.") |
| 560 | + |
529 | 561 | def calc_volume_from_ifc_shape(self): |
530 | 562 | # todo use more efficient iterator to calc all shapes at once |
531 | 563 | # with multiple cores: |
@@ -710,9 +742,9 @@ class Factory: |
710 | 742 | https://refactoring.guru/design-patterns/factory-method/python/example |
711 | 743 |
|
712 | 744 | Example: |
713 | | - factory = Factory([Pipe, Boiler], dummy) |
714 | | - ele = factory(some_ifc_element) |
715 | | - """ |
| 745 | + >>> factory = Factory([Pipe, Boiler], dummy) |
| 746 | + >>> ele = factory(some_ifc_element) |
| 747 | + """ |
716 | 748 |
|
717 | 749 | def __init__( |
718 | 750 | self, |
@@ -882,14 +914,68 @@ def __init__(self, element): |
882 | 914 | if self.is_picklable(value): |
883 | 915 | setattr(self, attr_name, value) |
884 | 916 | else: |
885 | | - logger.info( |
886 | | - f"Attribute {attr_name} will not be serialized, as it's " |
887 | | - f"not pickleable") |
888 | | - if hasattr(element, "space_boundaries"): |
889 | | - self.space_boundaries = [bound.guid for bound in |
890 | | - element.space_boundaries] |
891 | | - if hasattr(element, "storeys"): |
892 | | - self.storeys = [storey.guid for storey in element.storeys] |
| 917 | + try: |
| 918 | + logger.info( |
| 919 | + f"Attribute {attr_name} will not be serialized, as it's " |
| 920 | + f"not pickleable, trying to add alternative " |
| 921 | + f"information instead.") |
| 922 | + if isinstance(value, (list, tuple)): |
| 923 | + temp_list = [] |
| 924 | + for val in value: |
| 925 | + if hasattr(val, 'guid'): |
| 926 | + temp_list.append(val.guid) |
| 927 | + setattr(self, attr_name, temp_list) |
| 928 | + logger.info(f"Successfully linked a list of guids.") |
| 929 | + elif isinstance(value, str): |
| 930 | + setattr(self, attr_name, value) |
| 931 | + logger.info(f"Successfully linked value as string.") |
| 932 | + elif hasattr(value, 'guid'): |
| 933 | + setattr(self, attr_name, value.guid) |
| 934 | + logger.info(f"Successfully linked a single guid.") |
| 935 | + elif hasattr(value, 'Coord'): |
| 936 | + setattr(self, attr_name, value.Coord()) |
| 937 | + logger.info(f"Successfully linked a coordinate tuple.") |
| 938 | + elif value is None: |
| 939 | + setattr(self, attr_name, None) |
| 940 | + logger.info(f"Successfully set attribute value to " |
| 941 | + f"None.") |
| 942 | + else: |
| 943 | + logger.info("Linking alternative pickleable attributes " |
| 944 | + "failed.") |
| 945 | + except AttributeError: |
| 946 | + logger.info(f"Linking attribute failed.") |
| 947 | + for attr_name, attr_val in vars(element).items(): |
| 948 | + if hasattr(self, attr_name) or attr_name == 'attributes': |
| 949 | + continue |
| 950 | + else: |
| 951 | + logger.info(f"Try to add attribute data for attribute " |
| 952 | + f"'{attr_name}' that is not in AttributeManager.") |
| 953 | + value = attr_val |
| 954 | + if isinstance(value, (list, tuple)): |
| 955 | + temp_list = [] |
| 956 | + for val in value: |
| 957 | + if hasattr(val, 'guid'): |
| 958 | + temp_list.append(val.guid) |
| 959 | + setattr(self, attr_name, temp_list) |
| 960 | + logger.info( |
| 961 | + f"Successfully linked a list of guids.") |
| 962 | + elif isinstance(value, str): |
| 963 | + setattr(self, attr_name, value) |
| 964 | + logger.info( |
| 965 | + f"Successfully added {attr_name} as string.") |
| 966 | + elif hasattr(value, 'guid'): |
| 967 | + setattr(self, attr_name, value.guid) |
| 968 | + logger.info(f"Successfully linked a single guid.") |
| 969 | + elif hasattr(value, 'Coord'): |
| 970 | + setattr(self, attr_name, value.Coord()) |
| 971 | + logger.info(f"Successfully linked a coordinate tuple.") |
| 972 | + elif value is None: |
| 973 | + setattr(self, attr_name, None) |
| 974 | + logger.info(f"Successfully set attribute value to " |
| 975 | + f"None.") |
| 976 | + else: |
| 977 | + logger.info("Linking alternative pickleable attributes " |
| 978 | + "failed.") |
893 | 979 | if issubclass(element.__class__, AggregationMixin): |
894 | 980 | self.elements = [ele.guid for ele in element.elements] |
895 | 981 |
|
|
0 commit comments