ifctrano 0.1.12__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,227 @@
1
+ import logging
2
+ from typing import List, Optional
3
+
4
+ from ifcopenshell import file, entity_instance
5
+
6
+ from pydantic import BaseModel
7
+ from trano.elements.construction import ( # type: ignore
8
+ Material,
9
+ Layer,
10
+ Construction,
11
+ GlassMaterial,
12
+ Gas,
13
+ Glass,
14
+ GlassLayer,
15
+ GasLayer,
16
+ )
17
+ from ifctrano.utils import remove_non_alphanumeric, generate_alphanumeric_uuid
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ DEFAULT_MATERIAL = {
23
+ "thermal_conductivity": 0.046,
24
+ "specific_heat_capacity": 940,
25
+ "density": 80,
26
+ }
27
+
28
+
29
+ material_1 = Material(
30
+ name="material_1",
31
+ thermal_conductivity=0.046,
32
+ specific_heat_capacity=940,
33
+ density=80,
34
+ )
35
+ default_construction = Construction(
36
+ name="default_construction",
37
+ layers=[
38
+ Layer(material=material_1, thickness=0.18),
39
+ ],
40
+ )
41
+ id_100 = GlassMaterial(
42
+ name="id_100",
43
+ thermal_conductivity=1,
44
+ density=2500,
45
+ specific_heat_capacity=840,
46
+ solar_transmittance=[0.646],
47
+ solar_reflectance_outside_facing=[0.062],
48
+ solar_reflectance_room_facing=[0.063],
49
+ infrared_transmissivity=0,
50
+ infrared_absorptivity_outside_facing=0.84,
51
+ infrared_absorptivity_room_facing=0.84,
52
+ )
53
+ air = Gas(
54
+ name="Air",
55
+ thermal_conductivity=0.025,
56
+ density=1.2,
57
+ specific_heat_capacity=1005,
58
+ )
59
+ glass = Glass(
60
+ name="double_glazing",
61
+ u_value_frame=1.4,
62
+ layers=[
63
+ GlassLayer(thickness=0.003, material=id_100),
64
+ GasLayer(thickness=0.0127, material=air),
65
+ GlassLayer(thickness=0.003, material=id_100),
66
+ ],
67
+ )
68
+
69
+
70
+ class MaterialId(Material): # type: ignore
71
+ id: int
72
+
73
+ def to_material(self) -> Material:
74
+ return Material.model_validate(self.model_dump(exclude={"id"}))
75
+
76
+
77
+ class LayerId(Layer): # type: ignore
78
+ id: int
79
+
80
+ def to_layer(self) -> Layer:
81
+ return Layer.model_validate(self.model_dump(exclude={"id"}))
82
+
83
+
84
+ class ConstructionId(Construction): # type: ignore
85
+ id: int
86
+
87
+ def to_construction(self) -> Construction:
88
+ return Construction.model_validate(self.model_dump(exclude={"id"}))
89
+
90
+
91
+ class Materials(BaseModel):
92
+ materials: List[MaterialId]
93
+
94
+ @classmethod
95
+ def from_ifc(cls, ifc_file: file) -> "Materials":
96
+ materials = ifc_file.by_type("IfcMaterial")
97
+ return cls.from_ifc_materials(materials)
98
+
99
+ @classmethod
100
+ def from_ifc_materials(cls, ifc_materials: List[entity_instance]) -> "Materials":
101
+ materials = []
102
+ for material in ifc_materials:
103
+ material_name = remove_non_alphanumeric(material.Name)
104
+ materials.append(
105
+ MaterialId.model_validate(
106
+ {"name": material_name, "id": material.id(), **DEFAULT_MATERIAL}
107
+ )
108
+ )
109
+ return cls(materials=materials)
110
+
111
+ def get_material(self, id: int) -> Material:
112
+ for material in self.materials:
113
+ if material.id == id:
114
+ return material.to_material()
115
+ raise ValueError(f"Material {id} not found in materials list.")
116
+
117
+
118
+ def _get_unit_factor(ifc_file: file) -> float:
119
+ length_unit = next(
120
+ unit for unit in ifc_file.by_type("IfcSiUnit") if unit.UnitType == "LENGTHUNIT"
121
+ )
122
+ if length_unit.Prefix == "MILLI" and length_unit.Name == "METRE":
123
+ return 0.001
124
+ return 1
125
+
126
+
127
+ class Layers(BaseModel):
128
+ layers: List[LayerId]
129
+
130
+ @classmethod
131
+ def from_ifc(cls, ifc_file: file, materials: Materials) -> "Layers":
132
+ material_layers = ifc_file.by_type("IfcMaterialLayer")
133
+ unit_factor = _get_unit_factor(ifc_file)
134
+ return cls.from_ifc_material_layers(
135
+ material_layers, materials, unit_factor=unit_factor
136
+ )
137
+
138
+ @classmethod
139
+ def from_ifc_material_layers(
140
+ cls,
141
+ ifc_material_layers: List[entity_instance],
142
+ materials: Materials,
143
+ unit_factor: float = 1,
144
+ ) -> "Layers":
145
+ layers = []
146
+ for layer in ifc_material_layers:
147
+ thickness = layer.LayerThickness * unit_factor
148
+ layers.append(
149
+ LayerId(
150
+ id=layer.id(),
151
+ thickness=thickness,
152
+ material=materials.get_material(layer.Material.id()),
153
+ )
154
+ )
155
+ return cls(layers=layers)
156
+
157
+ def from_ids(self, ids: List[int]) -> List[Layer]:
158
+ return [layer.to_layer() for layer in self.layers if layer.id in ids]
159
+
160
+
161
+ class Constructions(BaseModel):
162
+ constructions: List[ConstructionId]
163
+
164
+ @classmethod
165
+ def from_ifc(cls, ifc_file: file) -> "Constructions":
166
+ materials = Materials.from_ifc(ifc_file)
167
+ layers = Layers.from_ifc(ifc_file, materials)
168
+ material_layers_sets = ifc_file.by_type("IfcMaterialLayerSet")
169
+ return cls.from_ifc_material_layer_sets(material_layers_sets, layers)
170
+
171
+ @classmethod
172
+ def from_ifc_material_layer_sets(
173
+ cls, ifc_material_layer_sets: List[entity_instance], layers: Layers
174
+ ) -> "Constructions":
175
+ constructions = []
176
+ for layer_set in ifc_material_layer_sets:
177
+ name_ = layer_set.LayerSetName or generate_alphanumeric_uuid()
178
+ name = remove_non_alphanumeric(name_)
179
+ layer_ids = [
180
+ int(material_layer.id()) for material_layer in layer_set.MaterialLayers
181
+ ]
182
+ constructions.append(
183
+ ConstructionId(
184
+ id=layer_set.id(), name=name, layers=layers.from_ids(layer_ids)
185
+ )
186
+ )
187
+ return cls(constructions=constructions)
188
+
189
+ def get_construction(self, entity: entity_instance) -> Construction:
190
+ construction_id = self._get_construction_id(entity)
191
+ if construction_id is None:
192
+ logger.error(
193
+ f"Construction ID not found for {entity.GlobalId} ({entity.is_a()}) "
194
+ f"({entity.Name}). Using default construction."
195
+ )
196
+ return default_construction
197
+ constructions = [
198
+ construction.to_construction()
199
+ for construction in self.constructions
200
+ if construction.id == construction_id
201
+ ]
202
+ if not constructions:
203
+ raise ValueError(f"No construction found for {entity.GlobalId}")
204
+ return constructions[0]
205
+
206
+ def _get_construction_id(self, entity: entity_instance) -> Optional[int]:
207
+ associates_materials = [
208
+ association
209
+ for association in entity.HasAssociations
210
+ if association.is_a() == "IfcRelAssociatesMaterial"
211
+ ]
212
+ if not associates_materials:
213
+ logger.error(f"Associate materials not found for {entity.GlobalId}.")
214
+ return None
215
+ relating_material = associates_materials[0].RelatingMaterial
216
+ if relating_material.is_a() == "IfcMaterialList":
217
+ logger.error(
218
+ f"Material list found for {entity.GlobalId}, but no construction ID available."
219
+ )
220
+ return None
221
+ elif relating_material.is_a() == "IfcMaterialLayerSetUsage":
222
+ return int(associates_materials[0].RelatingMaterial.ForLayerSet.id())
223
+ elif relating_material.is_a() == "IfcMaterialLayerSet":
224
+ return int(relating_material.id())
225
+ else:
226
+ logger.error("Unexpected material type found.")
227
+ return None