ifctrano 0.7.0__py3-none-any.whl → 0.9.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/building.py +110 -2
- ifctrano/construction.py +91 -3
- ifctrano/main.py +32 -0
- ifctrano/space_boundary.py +157 -5
- {ifctrano-0.7.0.dist-info → ifctrano-0.9.0.dist-info}/METADATA +2 -2
- ifctrano-0.9.0.dist-info/RECORD +16 -0
- ifctrano-0.7.0.dist-info/RECORD +0 -16
- {ifctrano-0.7.0.dist-info → ifctrano-0.9.0.dist-info}/WHEEL +0 -0
- {ifctrano-0.7.0.dist-info → ifctrano-0.9.0.dist-info}/entry_points.txt +0 -0
- {ifctrano-0.7.0.dist-info → ifctrano-0.9.0.dist-info}/licenses/LICENSE +0 -0
ifctrano/building.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
import logging
|
2
2
|
import re
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import List, Tuple, Any, Optional, Set
|
4
|
+
from typing import List, Tuple, Any, Optional, Set, Dict
|
5
5
|
|
6
6
|
import ifcopenshell
|
7
|
+
import yaml
|
7
8
|
from ifcopenshell import file, entity_instance
|
8
9
|
from pydantic import validate_call, Field, model_validator, field_validator
|
9
10
|
from trano.elements import InternalElement # type: ignore
|
11
|
+
from trano.elements.envelope import SpaceTilt # type: ignore
|
10
12
|
from trano.elements.library.library import Library # type: ignore
|
11
13
|
from trano.elements.types import Tilt # type: ignore
|
12
14
|
from trano.topology import Network # type: ignore
|
@@ -23,7 +25,11 @@ from ifctrano.space_boundary import (
|
|
23
25
|
initialize_tree,
|
24
26
|
Space,
|
25
27
|
)
|
26
|
-
from ifctrano.construction import
|
28
|
+
from ifctrano.construction import (
|
29
|
+
Constructions,
|
30
|
+
default_construction,
|
31
|
+
default_internal_construction,
|
32
|
+
)
|
27
33
|
|
28
34
|
logger = logging.getLogger(__name__)
|
29
35
|
|
@@ -215,6 +221,83 @@ class Building(BaseShow):
|
|
215
221
|
def get_adjacency(self) -> InternalElements:
|
216
222
|
return get_internal_elements(self.space_boundaries)
|
217
223
|
|
224
|
+
@validate_call
|
225
|
+
def to_config(
|
226
|
+
self,
|
227
|
+
north_axis: Optional[Vector] = None,
|
228
|
+
) -> Dict[str, Any]:
|
229
|
+
north_axis = north_axis or Vector(x=0, y=1, z=0)
|
230
|
+
spaces = [
|
231
|
+
space_boundary.to_config(
|
232
|
+
self.internal_elements.internal_element_ids(),
|
233
|
+
north_axis,
|
234
|
+
self.constructions,
|
235
|
+
)
|
236
|
+
for space_boundary in self.space_boundaries
|
237
|
+
]
|
238
|
+
spaces = [sp for sp in spaces if sp is not None]
|
239
|
+
internal_walls = []
|
240
|
+
|
241
|
+
for internal_element in self.internal_elements.elements:
|
242
|
+
space_1 = internal_element.spaces[0]
|
243
|
+
space_2 = internal_element.spaces[1]
|
244
|
+
if any(
|
245
|
+
name not in [sp["id"] for sp in spaces] # type: ignore
|
246
|
+
for name in [
|
247
|
+
space_1.space_unique_name(),
|
248
|
+
space_2.space_unique_name(),
|
249
|
+
]
|
250
|
+
):
|
251
|
+
continue
|
252
|
+
|
253
|
+
construction = self.constructions.get_construction(
|
254
|
+
internal_element.element, default_internal_construction
|
255
|
+
)
|
256
|
+
if internal_element.element.is_a() in ["IfcSlab"]:
|
257
|
+
space_1_tilt = (
|
258
|
+
Tilt.floor.value
|
259
|
+
if space_1.bounding_box.centroid.z > space_2.bounding_box.centroid.z
|
260
|
+
else Tilt.ceiling.value
|
261
|
+
)
|
262
|
+
space_2_tilt = (
|
263
|
+
Tilt.floor.value
|
264
|
+
if space_2.bounding_box.centroid.z > space_1.bounding_box.centroid.z
|
265
|
+
else Tilt.ceiling.value
|
266
|
+
)
|
267
|
+
if space_1_tilt == space_2_tilt:
|
268
|
+
logger.error("Space tilts are not compatible.")
|
269
|
+
continue
|
270
|
+
internal_walls.append(
|
271
|
+
{
|
272
|
+
"space_1": space_1.space_unique_name(),
|
273
|
+
"space_2": space_2.space_unique_name(),
|
274
|
+
"construction": construction.name,
|
275
|
+
"surface": internal_element.area,
|
276
|
+
"space_1_tilt": space_1_tilt,
|
277
|
+
"space_2_tilt": space_2_tilt,
|
278
|
+
}
|
279
|
+
)
|
280
|
+
else:
|
281
|
+
internal_walls.append(
|
282
|
+
{
|
283
|
+
"space_1": space_1.space_unique_name(),
|
284
|
+
"space_2": space_2.space_unique_name(),
|
285
|
+
"construction": construction.name,
|
286
|
+
"surface": internal_element.area,
|
287
|
+
}
|
288
|
+
)
|
289
|
+
construction_config = self.constructions.to_config()
|
290
|
+
return construction_config | {
|
291
|
+
"spaces": spaces,
|
292
|
+
"internal_walls": internal_walls,
|
293
|
+
}
|
294
|
+
|
295
|
+
@validate_call
|
296
|
+
def to_yaml(self, yaml_path: Path, north_axis: Optional[Vector] = None) -> None:
|
297
|
+
config = self.to_config(north_axis=north_axis)
|
298
|
+
yaml_data = yaml.dump(config)
|
299
|
+
yaml_path.write_text(yaml_data)
|
300
|
+
|
218
301
|
@validate_call
|
219
302
|
def create_network(
|
220
303
|
self,
|
@@ -236,6 +319,30 @@ class Building(BaseShow):
|
|
236
319
|
for internal_element in self.internal_elements.elements:
|
237
320
|
space_1 = internal_element.spaces[0]
|
238
321
|
space_2 = internal_element.spaces[1]
|
322
|
+
if any(
|
323
|
+
global_id not in spaces
|
324
|
+
for global_id in [space_1.entity.GlobalId, space_2.entity.GlobalId]
|
325
|
+
):
|
326
|
+
continue
|
327
|
+
space_tilts = []
|
328
|
+
if internal_element.element.is_a() in ["IfcSlab"]:
|
329
|
+
space_1_tilt = (
|
330
|
+
Tilt.floor
|
331
|
+
if space_1.bounding_box.centroid.z > space_2.bounding_box.centroid.z
|
332
|
+
else Tilt.ceiling
|
333
|
+
)
|
334
|
+
space_2_tilt = (
|
335
|
+
Tilt.floor
|
336
|
+
if space_2.bounding_box.centroid.z > space_1.bounding_box.centroid.z
|
337
|
+
else Tilt.ceiling
|
338
|
+
)
|
339
|
+
if space_1_tilt == space_2_tilt:
|
340
|
+
logger.error("Space tilts are not compatible.")
|
341
|
+
continue
|
342
|
+
space_tilts = [
|
343
|
+
SpaceTilt(space_name=space_1.name, tilt=space_1_tilt),
|
344
|
+
SpaceTilt(space_name=space_2.name, tilt=space_2_tilt),
|
345
|
+
]
|
239
346
|
network.connect_spaces(
|
240
347
|
spaces[space_1.global_id],
|
241
348
|
spaces[space_2.global_id],
|
@@ -244,6 +351,7 @@ class Building(BaseShow):
|
|
244
351
|
construction=default_construction,
|
245
352
|
surface=internal_element.area,
|
246
353
|
tilt=Tilt.wall,
|
354
|
+
space_tilts=space_tilts,
|
247
355
|
),
|
248
356
|
)
|
249
357
|
return network
|
ifctrano/construction.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import List, Optional
|
2
|
+
from typing import List, Optional, Dict, Any
|
3
3
|
|
4
4
|
from ifcopenshell import file, entity_instance
|
5
5
|
|
@@ -38,6 +38,12 @@ default_construction = Construction(
|
|
38
38
|
Layer(material=material_1, thickness=0.18),
|
39
39
|
],
|
40
40
|
)
|
41
|
+
default_internal_construction = Construction(
|
42
|
+
name="default_internal_construction",
|
43
|
+
layers=[
|
44
|
+
Layer(material=material_1, thickness=0.18),
|
45
|
+
],
|
46
|
+
)
|
41
47
|
id_100 = GlassMaterial(
|
42
48
|
name="id_100",
|
43
49
|
thermal_conductivity=1,
|
@@ -186,14 +192,16 @@ class Constructions(BaseModel):
|
|
186
192
|
)
|
187
193
|
return cls(constructions=constructions)
|
188
194
|
|
189
|
-
def get_construction(
|
195
|
+
def get_construction(
|
196
|
+
self, entity: entity_instance, default: Optional[Construction] = None
|
197
|
+
) -> Construction:
|
190
198
|
construction_id = self._get_construction_id(entity)
|
191
199
|
if construction_id is None:
|
192
200
|
logger.warning(
|
193
201
|
f"Construction ID not found for {entity.GlobalId} ({entity.is_a()}). "
|
194
202
|
f"Using default construction."
|
195
203
|
)
|
196
|
-
return default_construction
|
204
|
+
return default or default_construction
|
197
205
|
constructions = [
|
198
206
|
construction.to_construction()
|
199
207
|
for construction in self.constructions
|
@@ -225,3 +233,83 @@ class Constructions(BaseModel):
|
|
225
233
|
else:
|
226
234
|
logger.error("Unexpected material type found.")
|
227
235
|
return None
|
236
|
+
|
237
|
+
def to_config(self) -> Dict[str, Any]:
|
238
|
+
constructions_all = [
|
239
|
+
*self.constructions,
|
240
|
+
default_construction,
|
241
|
+
glass,
|
242
|
+
default_internal_construction,
|
243
|
+
]
|
244
|
+
constructions = [
|
245
|
+
{
|
246
|
+
"id": construction.name,
|
247
|
+
"layers": [
|
248
|
+
{"material": layer.material.name, "thickness": layer.thickness}
|
249
|
+
for layer in construction.layers
|
250
|
+
],
|
251
|
+
}
|
252
|
+
for construction in constructions_all
|
253
|
+
if isinstance(construction, Construction)
|
254
|
+
]
|
255
|
+
glazings = [
|
256
|
+
{
|
257
|
+
"id": construction.name,
|
258
|
+
"layers": [
|
259
|
+
{
|
260
|
+
(
|
261
|
+
"glass" if isinstance(layer, GlassLayer) else "gas"
|
262
|
+
): layer.material.name,
|
263
|
+
"thickness": layer.thickness,
|
264
|
+
}
|
265
|
+
for layer in construction.layers
|
266
|
+
],
|
267
|
+
}
|
268
|
+
for construction in constructions_all
|
269
|
+
if isinstance(construction, Glass)
|
270
|
+
]
|
271
|
+
materials = {
|
272
|
+
layer.material
|
273
|
+
for construction in constructions_all
|
274
|
+
for layer in construction.layers
|
275
|
+
if type(layer.material) is Material
|
276
|
+
}
|
277
|
+
gas = {
|
278
|
+
layer.material
|
279
|
+
for construction in constructions_all
|
280
|
+
for layer in construction.layers
|
281
|
+
if type(layer.material) is Gas
|
282
|
+
}
|
283
|
+
glass_ = {
|
284
|
+
layer.material
|
285
|
+
for construction in constructions_all
|
286
|
+
for layer in construction.layers
|
287
|
+
if type(layer.material) is GlassMaterial
|
288
|
+
}
|
289
|
+
|
290
|
+
materials_ = [
|
291
|
+
(material.model_dump(exclude={"name"}) | {"id": material.name})
|
292
|
+
for material in materials
|
293
|
+
]
|
294
|
+
gas_ = [
|
295
|
+
(material.model_dump(exclude={"name"}) | {"id": material.name})
|
296
|
+
for material in gas
|
297
|
+
]
|
298
|
+
glass_material = [
|
299
|
+
(_convert_glass(material) | {"id": material.name}) for material in glass_
|
300
|
+
]
|
301
|
+
return {
|
302
|
+
"constructions": constructions,
|
303
|
+
"material": materials_,
|
304
|
+
"glazings": glazings,
|
305
|
+
"gas": gas_,
|
306
|
+
"glass_material": glass_material,
|
307
|
+
}
|
308
|
+
|
309
|
+
|
310
|
+
def _convert_glass(glass_: Material) -> Dict[str, Any]:
|
311
|
+
return {
|
312
|
+
key: (value if not isinstance(value, list) else value)
|
313
|
+
for key, value in glass_.model_dump().items()
|
314
|
+
if key not in ["name"]
|
315
|
+
}
|
ifctrano/main.py
CHANGED
@@ -22,6 +22,38 @@ CHECKMARK = "[green]✔[/green]"
|
|
22
22
|
CROSS_MARK = "[red]✘[/red]"
|
23
23
|
|
24
24
|
|
25
|
+
@app.command()
|
26
|
+
def config(
|
27
|
+
model: Annotated[
|
28
|
+
str,
|
29
|
+
typer.Argument(help="Local path to the ifc file."),
|
30
|
+
],
|
31
|
+
show_space_boundaries: Annotated[
|
32
|
+
bool,
|
33
|
+
typer.Option(help="Show computed space boundaries."),
|
34
|
+
] = False,
|
35
|
+
) -> None:
|
36
|
+
working_directory = Path.cwd()
|
37
|
+
with Progress(
|
38
|
+
SpinnerColumn(),
|
39
|
+
TextColumn("[progress.description]{task.description}"),
|
40
|
+
transient=True,
|
41
|
+
) as progress:
|
42
|
+
modelica_model_path = Path(model).resolve().with_suffix(".mo")
|
43
|
+
config_path = working_directory.joinpath(f"{modelica_model_path.stem}.yaml")
|
44
|
+
task = progress.add_task(
|
45
|
+
description=f"Generating {config_path} configuration file.",
|
46
|
+
total=None,
|
47
|
+
)
|
48
|
+
building = Building.from_ifc(Path(model))
|
49
|
+
if show_space_boundaries:
|
50
|
+
print(f"{CHECKMARK} Showing space boundaries.")
|
51
|
+
building.show()
|
52
|
+
building.to_yaml(config_path)
|
53
|
+
progress.remove_task(task)
|
54
|
+
print(f"{CHECKMARK} configuration file generated: {config_path}.")
|
55
|
+
|
56
|
+
|
25
57
|
@app.command()
|
26
58
|
def create(
|
27
59
|
model: Annotated[
|
ifctrano/space_boundary.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
import logging
|
2
|
+
import math
|
1
3
|
import multiprocessing
|
2
|
-
from typing import Optional, List, Tuple, Any, Annotated
|
4
|
+
from typing import Optional, List, Tuple, Any, Annotated, Dict
|
3
5
|
|
4
6
|
import ifcopenshell
|
5
7
|
import ifcopenshell.geom
|
@@ -23,8 +25,7 @@ from ifctrano.base import (
|
|
23
25
|
BaseShow,
|
24
26
|
)
|
25
27
|
from ifctrano.bounding_box import OrientedBoundingBox
|
26
|
-
from ifctrano.construction import glass, Constructions
|
27
|
-
from ifctrano.exceptions import HasWindowsWithoutWallsError
|
28
|
+
from ifctrano.construction import glass, Constructions, default_construction
|
28
29
|
from ifctrano.utils import (
|
29
30
|
remove_non_alphanumeric,
|
30
31
|
_round,
|
@@ -34,6 +35,8 @@ from ifctrano.utils import (
|
|
34
35
|
|
35
36
|
ROOF_VECTOR = Vector(x=0, y=0, z=1)
|
36
37
|
|
38
|
+
logger = logging.getLogger(__name__)
|
39
|
+
|
37
40
|
|
38
41
|
def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
|
39
42
|
tree = ifcopenshell.geom.tree()
|
@@ -89,6 +92,14 @@ class Space(GlobalId):
|
|
89
92
|
main_name = f"{remove_non_alphanumeric(self.name)}_" if self.name else ""
|
90
93
|
return f"space_{main_name}{remove_non_alphanumeric(self.entity.GlobalId)}"
|
91
94
|
|
95
|
+
def space_unique_name(self) -> str:
|
96
|
+
base_name = remove_non_alphanumeric(self.name) if self.name else ""
|
97
|
+
main_name = f"{base_name}_" if base_name else ""
|
98
|
+
space_name = f"{main_name}{remove_non_alphanumeric(self.entity.GlobalId)[-3:]}"
|
99
|
+
if "space" not in space_name:
|
100
|
+
return f"space_{space_name}"
|
101
|
+
return space_name
|
102
|
+
|
92
103
|
|
93
104
|
class ExternalSpaceBoundaryGroup(BaseModelConfig):
|
94
105
|
constructions: List[BaseWall]
|
@@ -109,11 +120,57 @@ class ExternalSpaceBoundaryGroup(BaseModelConfig):
|
|
109
120
|
for construction in self.constructions
|
110
121
|
)
|
111
122
|
|
123
|
+
def _merge(
|
124
|
+
self,
|
125
|
+
constructions: List[Window | ExternalWall],
|
126
|
+
construction_type: type[Window | ExternalWall],
|
127
|
+
) -> List[Window | ExternalWall]:
|
128
|
+
construction_types = [
|
129
|
+
c for c in self.constructions if isinstance(c, construction_type)
|
130
|
+
]
|
131
|
+
reference = next(iter(construction_types), None)
|
132
|
+
if not reference:
|
133
|
+
return []
|
134
|
+
surface = sum([construction.surface for construction in construction_types])
|
135
|
+
azimuth = reference.azimuth
|
136
|
+
tilt = reference.tilt
|
137
|
+
return [
|
138
|
+
construction_type(
|
139
|
+
surface=surface,
|
140
|
+
azimuth=azimuth,
|
141
|
+
tilt=tilt,
|
142
|
+
construction=reference.construction,
|
143
|
+
)
|
144
|
+
]
|
145
|
+
|
146
|
+
def merge(self) -> None:
|
147
|
+
self.constructions = [
|
148
|
+
*self._merge(self.constructions, Window),
|
149
|
+
*self._merge(self.constructions, ExternalWall),
|
150
|
+
]
|
151
|
+
|
152
|
+
def add_external_wall(self) -> None:
|
153
|
+
reference = next(iter(self.constructions))
|
154
|
+
surface = sum([construction.surface for construction in self.constructions]) * (
|
155
|
+
0.7 / 0.3
|
156
|
+
)
|
157
|
+
azimuth = reference.azimuth
|
158
|
+
tilt = reference.tilt
|
159
|
+
self.constructions.append(
|
160
|
+
ExternalWall(
|
161
|
+
surface=surface,
|
162
|
+
azimuth=azimuth,
|
163
|
+
tilt=tilt,
|
164
|
+
construction=default_construction,
|
165
|
+
)
|
166
|
+
)
|
167
|
+
|
112
168
|
|
113
169
|
class ExternalSpaceBoundaryGroups(BaseModelConfig):
|
114
170
|
space_boundary_groups: List[ExternalSpaceBoundaryGroup] = Field(
|
115
171
|
default_factory=list
|
116
172
|
)
|
173
|
+
remaining_constructions: List[BaseWall]
|
117
174
|
|
118
175
|
@classmethod
|
119
176
|
def from_external_boundaries(
|
@@ -124,6 +181,11 @@ class ExternalSpaceBoundaryGroups(BaseModelConfig):
|
|
124
181
|
for ex in external_boundaries
|
125
182
|
if isinstance(ex, (ExternalWall, Window)) and ex.tilt == Tilt.wall
|
126
183
|
]
|
184
|
+
remaining_constructions = [
|
185
|
+
ex
|
186
|
+
for ex in external_boundaries
|
187
|
+
if not (isinstance(ex, (ExternalWall, Window)) and ex.tilt == Tilt.wall)
|
188
|
+
]
|
127
189
|
space_boundary_groups = list(
|
128
190
|
{
|
129
191
|
ExternalSpaceBoundaryGroup(
|
@@ -138,7 +200,12 @@ class ExternalSpaceBoundaryGroups(BaseModelConfig):
|
|
138
200
|
for ex in boundary_walls
|
139
201
|
}
|
140
202
|
)
|
141
|
-
|
203
|
+
groups = cls(
|
204
|
+
space_boundary_groups=space_boundary_groups,
|
205
|
+
remaining_constructions=remaining_constructions,
|
206
|
+
)
|
207
|
+
groups.merge()
|
208
|
+
return groups
|
142
209
|
|
143
210
|
def has_windows_without_wall(self) -> bool:
|
144
211
|
return all(
|
@@ -146,6 +213,24 @@ class ExternalSpaceBoundaryGroups(BaseModelConfig):
|
|
146
213
|
for group in self.space_boundary_groups
|
147
214
|
)
|
148
215
|
|
216
|
+
def add_external_walls(self) -> None:
|
217
|
+
for group in self.space_boundary_groups:
|
218
|
+
group.add_external_wall()
|
219
|
+
|
220
|
+
def get_constructions(self) -> List[ExternalWall | Window]:
|
221
|
+
return [
|
222
|
+
*[c for group in self.space_boundary_groups for c in group.constructions],
|
223
|
+
*self.remaining_constructions,
|
224
|
+
]
|
225
|
+
|
226
|
+
def merge(self) -> None:
|
227
|
+
for g in self.space_boundary_groups:
|
228
|
+
g.merge()
|
229
|
+
|
230
|
+
|
231
|
+
def deg_to_rad(deg: float) -> float:
|
232
|
+
return round(deg * math.pi / 180.0, 2)
|
233
|
+
|
149
234
|
|
150
235
|
class Azimuths(BaseModel):
|
151
236
|
north: List[float] = [0.0, 360]
|
@@ -278,6 +363,70 @@ class SpaceBoundaries(BaseShow):
|
|
278
363
|
if space_boundary in self.boundaries:
|
279
364
|
self.boundaries.remove(space_boundary)
|
280
365
|
|
366
|
+
def to_config(
|
367
|
+
self,
|
368
|
+
exclude_entities: List[str],
|
369
|
+
north_axis: Vector,
|
370
|
+
constructions: Constructions,
|
371
|
+
) -> Optional[Dict[str, Any]]:
|
372
|
+
external_boundaries: Dict[str, Any] = {
|
373
|
+
"external_walls": [],
|
374
|
+
"floor_on_grounds": [],
|
375
|
+
"windows": [],
|
376
|
+
}
|
377
|
+
external_boundaries_check = []
|
378
|
+
for boundary in self.boundaries:
|
379
|
+
boundary_model = boundary.model_element(
|
380
|
+
exclude_entities, north_axis, constructions
|
381
|
+
)
|
382
|
+
if boundary_model:
|
383
|
+
external_boundaries_check.append(boundary_model)
|
384
|
+
if not external_boundaries_check:
|
385
|
+
return None
|
386
|
+
external_space_boundaries_group = (
|
387
|
+
ExternalSpaceBoundaryGroups.from_external_boundaries(
|
388
|
+
external_boundaries_check
|
389
|
+
)
|
390
|
+
)
|
391
|
+
if not external_space_boundaries_group.has_windows_without_wall():
|
392
|
+
external_space_boundaries_group.add_external_walls()
|
393
|
+
logger.error(
|
394
|
+
f"Space {self.space.global_id} has a boundary that has a windows but without walls."
|
395
|
+
)
|
396
|
+
for boundary_model in external_space_boundaries_group.get_constructions():
|
397
|
+
element = {
|
398
|
+
"surface": boundary_model.surface,
|
399
|
+
"azimuth": deg_to_rad(boundary_model.azimuth),
|
400
|
+
"tilt": boundary_model.tilt.value,
|
401
|
+
"construction": boundary_model.construction.name,
|
402
|
+
}
|
403
|
+
if isinstance(
|
404
|
+
boundary_model, (ExternalWall, ExternalDoor)
|
405
|
+
) and boundary_model.tilt in [Tilt.wall, Tilt.ceiling]:
|
406
|
+
external_boundaries["external_walls"].append(element)
|
407
|
+
elif isinstance(boundary_model, (Window)):
|
408
|
+
external_boundaries["windows"].append(element)
|
409
|
+
elif isinstance(boundary_model, (ExternalWall)) and boundary_model.tilt in [
|
410
|
+
Tilt.floor
|
411
|
+
]:
|
412
|
+
element.pop("azimuth")
|
413
|
+
element.pop("tilt")
|
414
|
+
external_boundaries["floor_on_grounds"].append(element)
|
415
|
+
else:
|
416
|
+
raise ValueError("Unknown boundary type")
|
417
|
+
|
418
|
+
occupancy_parameters = Occupancy().parameters.model_dump(mode="json")
|
419
|
+
space_parameters = SpaceParameter(
|
420
|
+
floor_area=self.space.floor_area,
|
421
|
+
average_room_height=self.space.average_room_height,
|
422
|
+
).model_dump(mode="json", exclude={"linearize_emissive_power", "volume"})
|
423
|
+
return {
|
424
|
+
"id": self.space.space_unique_name(),
|
425
|
+
"occupancy": {"parameters": occupancy_parameters},
|
426
|
+
"parameters": space_parameters,
|
427
|
+
"external_boundaries": external_boundaries,
|
428
|
+
}
|
429
|
+
|
281
430
|
def model(
|
282
431
|
self,
|
283
432
|
exclude_entities: List[str],
|
@@ -291,12 +440,15 @@ class SpaceBoundaries(BaseShow):
|
|
291
440
|
)
|
292
441
|
if boundary_model:
|
293
442
|
external_boundaries.append(boundary_model)
|
443
|
+
if not external_boundaries:
|
444
|
+
return None
|
294
445
|
|
295
446
|
external_space_boundaries_group = (
|
296
447
|
ExternalSpaceBoundaryGroups.from_external_boundaries(external_boundaries)
|
297
448
|
)
|
298
449
|
if not external_space_boundaries_group.has_windows_without_wall():
|
299
|
-
|
450
|
+
external_space_boundaries_group.add_external_walls()
|
451
|
+
logger.error(
|
300
452
|
f"Space {self.space.global_id} has a boundary that has a windows but without walls."
|
301
453
|
)
|
302
454
|
return TranoSpace(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ifctrano
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: Package for generating building energy simulation model from IFC
|
5
5
|
License: GPL V3
|
6
6
|
License-File: LICENSE
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Requires-Dist: ifcopenshell (>=0.8.1.post1,<0.9.0)
|
17
17
|
Requires-Dist: open3d (>=0.19.0,<0.20.0)
|
18
18
|
Requires-Dist: shapely (>=2.0.7,<3.0.0)
|
19
|
-
Requires-Dist: trano (>=0.
|
19
|
+
Requires-Dist: trano (>=0.12.0,<0.13.0)
|
20
20
|
Requires-Dist: typer (>=0.12.5,<0.13.0)
|
21
21
|
Requires-Dist: vedo (>=2025.5.3,<2026.0.0)
|
22
22
|
Project-URL: Repository, https://github.com/andoludo/ifctrano
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ifctrano/__init__.py,sha256=6wK0Qa_5Xe0KSk5Spn98AjTNCTIXtenZ3C0k8ZaVcn8,191
|
2
|
+
ifctrano/base.py,sha256=s8BSnQqd6v_NFvkQFRriHCine6iylUBQai2FduC4IFk,14352
|
3
|
+
ifctrano/bounding_box.py,sha256=64jlzY60ZDUX4ipR34WkOICpPhxLP-5w-FP42yBONRs,7729
|
4
|
+
ifctrano/building.py,sha256=MgYGyCXKbLXKpqtuokLlds_b1JO3cfOSnDkK7tf1wA0,13583
|
5
|
+
ifctrano/construction.py,sha256=I8oRRd1KbG-_sy40-X3PXIz4VCcmbR68WaJcWdpwDIA,10067
|
6
|
+
ifctrano/example/verification.ifc,sha256=tQ9QcubT_wrbb-sc1WRRwYpb2cbkWm3dnRfXdP5GTTg,131
|
7
|
+
ifctrano/exceptions.py,sha256=JDy_0HV7_FLfr92DkrN8F59zUHl9KdMa_lfIcfBJG6I,540
|
8
|
+
ifctrano/main.py,sha256=W6Jx8ZN2Euoz44ECrU8xYetdSmdgkWBo3lngOVYXNU4,6053
|
9
|
+
ifctrano/space_boundary.py,sha256=VEQug6zAVtaCpm0YbX1l8UJt0MKmB0oL401JiXYX0Zg,21674
|
10
|
+
ifctrano/types.py,sha256=FBSWrD66EL7uHvv0GveCXmYyLtCr8gGRlT7-5j7aZD8,181
|
11
|
+
ifctrano/utils.py,sha256=zLQUGVo15RSQQ-vfOuHsJBkHy4QhpxKSAH7Au72s0s0,873
|
12
|
+
ifctrano-0.9.0.dist-info/METADATA,sha256=EIzRyrJexhRoyBcvMxI1dlVpkobTcsTJjTvA15NJer8,4884
|
13
|
+
ifctrano-0.9.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
14
|
+
ifctrano-0.9.0.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
|
15
|
+
ifctrano-0.9.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
16
|
+
ifctrano-0.9.0.dist-info/RECORD,,
|
ifctrano-0.7.0.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
ifctrano/__init__.py,sha256=6wK0Qa_5Xe0KSk5Spn98AjTNCTIXtenZ3C0k8ZaVcn8,191
|
2
|
-
ifctrano/base.py,sha256=s8BSnQqd6v_NFvkQFRriHCine6iylUBQai2FduC4IFk,14352
|
3
|
-
ifctrano/bounding_box.py,sha256=64jlzY60ZDUX4ipR34WkOICpPhxLP-5w-FP42yBONRs,7729
|
4
|
-
ifctrano/building.py,sha256=tBctMyrsvwRiDk5bDhAdfgRlbyvnbCCeA8rcdUjMbU8,9355
|
5
|
-
ifctrano/construction.py,sha256=Lyh_o37znrDyxtKgw7dOqVsQk1S3btCPDh2YTJ8h6T0,7234
|
6
|
-
ifctrano/example/verification.ifc,sha256=tQ9QcubT_wrbb-sc1WRRwYpb2cbkWm3dnRfXdP5GTTg,131
|
7
|
-
ifctrano/exceptions.py,sha256=JDy_0HV7_FLfr92DkrN8F59zUHl9KdMa_lfIcfBJG6I,540
|
8
|
-
ifctrano/main.py,sha256=YTp7RaWRMKt_UC2DRt_oXXLGTLCAc9FPg6Tsqg1zgpk,4979
|
9
|
-
ifctrano/space_boundary.py,sha256=PmiKhcJo6iyxXKNab5NCphQgBQwatoNsqsi-BYHOONs,16055
|
10
|
-
ifctrano/types.py,sha256=FBSWrD66EL7uHvv0GveCXmYyLtCr8gGRlT7-5j7aZD8,181
|
11
|
-
ifctrano/utils.py,sha256=zLQUGVo15RSQQ-vfOuHsJBkHy4QhpxKSAH7Au72s0s0,873
|
12
|
-
ifctrano-0.7.0.dist-info/METADATA,sha256=VAynLl3eaecRmVSgvU3XVSmO-_weipMqdfbw-pq3Blw,4884
|
13
|
-
ifctrano-0.7.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
14
|
-
ifctrano-0.7.0.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
|
15
|
-
ifctrano-0.7.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
16
|
-
ifctrano-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|