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
« 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
8if TYPE_CHECKING:
9 from bim2sim.elements.base_elements import ProductBased
11logger = logging.getLogger(__name__)
14class AggregationMixin:
15 guid_prefix = 'Agg'
16 multi = ()
17 aggregatable_classes: Set['ProductBased'] = set()
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)
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)
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
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
83 def source_info(self) -> str:
84 return f'[{", ".join(e.source_info() for e in self.elements)}]'
86 def __repr__(self):
87 return "<%s (aggregation of %d elements)>" % (
88 self.__class__.__name__, len(self.elements))
90 def __str__(self):
91 return "%s" % self.__class__.__name__