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.
- ifctrano/__init__.py +3 -0
- ifctrano/base.py +266 -13
- ifctrano/bounding_box.py +99 -169
- ifctrano/building.py +109 -54
- ifctrano/construction.py +227 -0
- ifctrano/example/verification.ifc +3 -3043
- ifctrano/exceptions.py +4 -0
- ifctrano/main.py +55 -2
- ifctrano/space_boundary.py +168 -98
- ifctrano/types.py +5 -0
- ifctrano/utils.py +29 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/METADATA +6 -5
- ifctrano-0.3.0.dist-info/RECORD +16 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/WHEEL +1 -1
- ifctrano-0.1.12.dist-info/RECORD +0 -13
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/LICENSE +0 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/entry_points.txt +0 -0
ifctrano/construction.py
ADDED
@@ -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
|