Coverage for bim2sim/elements/aggregation/__init__.py: 64%

47 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 17:09 +0000

1"""Package holds aggregations. Those aggregate multiple elements to one element. 

2E.g. multiple thermal zones into one thermal zone 

3""" 

4import logging 

5from typing import Set, Sequence, TYPE_CHECKING 

6import inspect 

7 

8if TYPE_CHECKING: 

9 from bim2sim.elements.base_elements import ProductBased 

10 

11logger = logging.getLogger(__name__) 

12 

13 

14class AggregationMixin: 

15 guid_prefix = 'Agg' 

16 multi = () 

17 aggregatable_classes: Set['ProductBased'] = set() 

18 

19 def __init__(self, elements: Sequence['ProductBased'], *args, **kwargs): 

20 if self.aggregatable_classes: 

21 received = {type(ele) for ele in elements} 

22 mismatch = received - self.aggregatable_classes 

23 if mismatch: 

24 raise AssertionError("Can't aggregate %s from elements: %s" % 

25 (self.__class__.__name__, mismatch)) 

26 # TODO: make guid reproduceable unique for same aggregation elements 

27 # e.g. hash of all (ordered?) element guids? 

28 # Needed for save/load decisions on aggregations 

29 self.elements = elements 

30 for model in self.elements: 

31 model.aggregation = self 

32 super().__init__(*args, **kwargs) 

33 

34 @classmethod 

35 def __init_subclass__(cls, **kwargs): 

36 from bim2sim.elements.base_elements import ProductBased 

37 super().__init_subclass__(**kwargs) 

38 if "Mixin" not in cls.__name__: 

39 if ProductBased not in inspect.getmro(cls): 

40 logger.error("%s only supports sub classes of ProductBased", cls) 

41 

42 def _calc_position(self, name): 

43 """Calculate the position based on first and last element.""" 

44 if not self.elements: 

45 logger.debug( 

46 f"No elements found for {self.__class__.__name__}" 

47 f" ({self.guid})") 

48 return None 

49 try: 

50 first_pos = self.elements[0].position 

51 last_pos = self.elements[-1].position 

52 return (first_pos + last_pos) / 2 

53 except AttributeError: 

54 logger.warning( 

55 f"Elements missing 'position' attribute in" 

56 f" {self.__class__.__name__} ({self.guid})" 

57 ) 

58 return None 

59 except Exception as ex: 

60 logger.warning( 

61 f"Position calculation failed for {self.__class__.__name__}" 

62 f" ({self.guid}): {ex}" 

63 ) 

64 return None 

65 

66 # def request(self, name): 

67 # # broadcast request to all nested elements 

68 # # if one attribute included in multi_calc is requested, all multi_calc attributes are needed 

69 # 

70 # if name in self.multi: 

71 # names = self.multi 

72 # else: 

73 # names = (name,) 

74 # 

75 # # for ele in self.elements: 

76 # # for n in names: 

77 # # ele.request(n) 

78 # decisions = DecisionBunch() 

79 # for n in names: 

80 # decisions.append(super().request(n)) 

81 # return decisions 

82 

83 def source_info(self) -> str: 

84 return f'[{", ".join(e.source_info() for e in self.elements)}]' 

85 

86 def __repr__(self): 

87 return "<%s (aggregation of %d elements)>" % ( 

88 self.__class__.__name__, len(self.elements)) 

89 

90 def __str__(self): 

91 return "%s" % self.__class__.__name__