ifctrano 0.4.0__py3-none-any.whl → 0.8.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/base.py +1 -0
- ifctrano/bounding_box.py +2 -0
- ifctrano/building.py +92 -6
- ifctrano/construction.py +87 -3
- ifctrano/exceptions.py +4 -0
- ifctrano/main.py +32 -0
- ifctrano/space_boundary.py +182 -3
- ifctrano/types.py +7 -1
- {ifctrano-0.4.0.dist-info → ifctrano-0.8.0.dist-info}/METADATA +4 -3
- ifctrano-0.8.0.dist-info/RECORD +16 -0
- {ifctrano-0.4.0.dist-info → ifctrano-0.8.0.dist-info}/WHEEL +1 -1
- ifctrano-0.4.0.dist-info/RECORD +0 -16
- {ifctrano-0.4.0.dist-info → ifctrano-0.8.0.dist-info}/entry_points.txt +0 -0
- {ifctrano-0.4.0.dist-info → ifctrano-0.8.0.dist-info/licenses}/LICENSE +0 -0
ifctrano/base.py
CHANGED
ifctrano/bounding_box.py
CHANGED
@@ -78,6 +78,7 @@ class ExtendCommonSurface(CommonSurface):
|
|
78
78
|
orientation=self.orientation,
|
79
79
|
main_vertices=self.main_vertices,
|
80
80
|
common_vertices=self.common_vertices,
|
81
|
+
polygon=self.polygon,
|
81
82
|
)
|
82
83
|
|
83
84
|
|
@@ -137,6 +138,7 @@ class OrientedBoundingBox(BaseShow):
|
|
137
138
|
common_vertices=projected_face_1.common_vertices(
|
138
139
|
intersection
|
139
140
|
),
|
141
|
+
polygon=intersection.wkt,
|
140
142
|
)
|
141
143
|
)
|
142
144
|
|
ifctrano/building.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
import logging
|
1
2
|
import re
|
2
3
|
from pathlib import Path
|
3
|
-
from typing import List, Tuple, Any, Optional, Set
|
4
|
+
from typing import List, Tuple, Any, Optional, Set, Dict
|
4
5
|
|
5
6
|
import ifcopenshell
|
7
|
+
import yaml
|
6
8
|
from ifcopenshell import file, entity_instance
|
7
9
|
from pydantic import validate_call, Field, model_validator, field_validator
|
8
10
|
from trano.elements import InternalElement # type: ignore
|
@@ -12,13 +14,23 @@ from trano.topology import Network # type: ignore
|
|
12
14
|
from vedo import Line # type: ignore
|
13
15
|
|
14
16
|
from ifctrano.base import BaseModelConfig, Libraries, Vector, BaseShow, CommonSurface
|
15
|
-
from ifctrano.exceptions import
|
17
|
+
from ifctrano.exceptions import (
|
18
|
+
IfcFileNotFoundError,
|
19
|
+
NoIfcSpaceFoundError,
|
20
|
+
NoSpaceBoundariesError,
|
21
|
+
)
|
16
22
|
from ifctrano.space_boundary import (
|
17
23
|
SpaceBoundaries,
|
18
24
|
initialize_tree,
|
19
25
|
Space,
|
20
26
|
)
|
21
|
-
from ifctrano.construction import
|
27
|
+
from ifctrano.construction import (
|
28
|
+
Constructions,
|
29
|
+
default_construction,
|
30
|
+
default_internal_construction,
|
31
|
+
)
|
32
|
+
|
33
|
+
logger = logging.getLogger(__name__)
|
22
34
|
|
23
35
|
|
24
36
|
def get_spaces(ifcopenshell_file: file) -> List[entity_instance]:
|
@@ -180,9 +192,18 @@ class Building(BaseShow):
|
|
180
192
|
]
|
181
193
|
if not spaces:
|
182
194
|
raise NoIfcSpaceFoundError("No IfcSpace found in the file.")
|
183
|
-
space_boundaries = [
|
184
|
-
|
185
|
-
|
195
|
+
space_boundaries = []
|
196
|
+
for space in spaces:
|
197
|
+
try:
|
198
|
+
space_boundaries.append(
|
199
|
+
SpaceBoundaries.from_space_entity(ifc_file, tree, space)
|
200
|
+
)
|
201
|
+
except Exception as e: # noqa: PERF203
|
202
|
+
logger.error(f"Cannot process space {space.id()}. Reason {e}")
|
203
|
+
continue
|
204
|
+
if not space_boundaries:
|
205
|
+
raise NoSpaceBoundariesError("No valid space boundaries found.")
|
206
|
+
|
186
207
|
return cls(
|
187
208
|
space_boundaries=space_boundaries,
|
188
209
|
ifc_file=ifc_file,
|
@@ -199,6 +220,71 @@ class Building(BaseShow):
|
|
199
220
|
def get_adjacency(self) -> InternalElements:
|
200
221
|
return get_internal_elements(self.space_boundaries)
|
201
222
|
|
223
|
+
@validate_call
|
224
|
+
def to_config(
|
225
|
+
self,
|
226
|
+
north_axis: Optional[Vector] = None,
|
227
|
+
) -> Dict[str, Any]:
|
228
|
+
north_axis = north_axis or Vector(x=0, y=1, z=0)
|
229
|
+
spaces = [
|
230
|
+
space_boundary.to_config(
|
231
|
+
self.internal_elements.internal_element_ids(),
|
232
|
+
north_axis,
|
233
|
+
self.constructions,
|
234
|
+
)
|
235
|
+
for space_boundary in self.space_boundaries
|
236
|
+
]
|
237
|
+
internal_walls = []
|
238
|
+
for internal_element in self.internal_elements.elements:
|
239
|
+
space_1 = internal_element.spaces[0]
|
240
|
+
space_2 = internal_element.spaces[1]
|
241
|
+
construction = self.constructions.get_construction(
|
242
|
+
internal_element.element, default_internal_construction
|
243
|
+
)
|
244
|
+
if internal_element.element.is_a() in ["IfcSlab"]:
|
245
|
+
space_1_tilt = (
|
246
|
+
Tilt.floor.value
|
247
|
+
if space_1.bounding_box.centroid.z > space_2.bounding_box.centroid.z
|
248
|
+
else Tilt.ceiling.value
|
249
|
+
)
|
250
|
+
space_2_tilt = (
|
251
|
+
Tilt.floor.value
|
252
|
+
if space_2.bounding_box.centroid.z > space_1.bounding_box.centroid.z
|
253
|
+
else Tilt.ceiling.value
|
254
|
+
)
|
255
|
+
if space_1_tilt == space_2_tilt:
|
256
|
+
raise ValueError("Space tilts are not compatible.")
|
257
|
+
internal_walls.append(
|
258
|
+
{
|
259
|
+
"space_1": space_1.space_unique_name(),
|
260
|
+
"space_2": space_2.space_unique_name(),
|
261
|
+
"construction": construction.name,
|
262
|
+
"surface": internal_element.area,
|
263
|
+
"space_1_tilt": space_1_tilt,
|
264
|
+
"space_2_tilt": space_2_tilt,
|
265
|
+
}
|
266
|
+
)
|
267
|
+
else:
|
268
|
+
internal_walls.append(
|
269
|
+
{
|
270
|
+
"space_1": space_1.space_unique_name(),
|
271
|
+
"space_2": space_2.space_unique_name(),
|
272
|
+
"construction": construction.name,
|
273
|
+
"surface": internal_element.area,
|
274
|
+
}
|
275
|
+
)
|
276
|
+
construction_config = self.constructions.to_config()
|
277
|
+
return construction_config | {
|
278
|
+
"spaces": spaces,
|
279
|
+
"internal_walls": internal_walls,
|
280
|
+
}
|
281
|
+
|
282
|
+
@validate_call
|
283
|
+
def to_yaml(self, yaml_path: Path, north_axis: Optional[Vector] = None) -> None:
|
284
|
+
config = self.to_config(north_axis=north_axis)
|
285
|
+
yaml_data = yaml.dump(config)
|
286
|
+
yaml_path.write_text(yaml_data)
|
287
|
+
|
202
288
|
@validate_call
|
203
289
|
def create_network(
|
204
290
|
self,
|
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,79 @@ 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 = [*self.constructions, default_construction, glass]
|
239
|
+
constructions = [
|
240
|
+
{
|
241
|
+
"id": construction.name,
|
242
|
+
"layers": [
|
243
|
+
{"material": layer.material.name, "thickness": layer.thickness}
|
244
|
+
for layer in construction.layers
|
245
|
+
],
|
246
|
+
}
|
247
|
+
for construction in constructions_all
|
248
|
+
if isinstance(construction, Construction)
|
249
|
+
]
|
250
|
+
glazings = [
|
251
|
+
{
|
252
|
+
"id": construction.name,
|
253
|
+
"layers": [
|
254
|
+
{
|
255
|
+
(
|
256
|
+
"glass" if isinstance(layer, GlassLayer) else "gas"
|
257
|
+
): layer.material.name,
|
258
|
+
"thickness": layer.thickness,
|
259
|
+
}
|
260
|
+
for layer in construction.layers
|
261
|
+
],
|
262
|
+
}
|
263
|
+
for construction in constructions_all
|
264
|
+
if isinstance(construction, Glass)
|
265
|
+
]
|
266
|
+
materials = {
|
267
|
+
layer.material
|
268
|
+
for construction in constructions_all
|
269
|
+
for layer in construction.layers
|
270
|
+
if type(layer.material) is Material
|
271
|
+
}
|
272
|
+
gas = {
|
273
|
+
layer.material
|
274
|
+
for construction in constructions_all
|
275
|
+
for layer in construction.layers
|
276
|
+
if type(layer.material) is Gas
|
277
|
+
}
|
278
|
+
glass_ = {
|
279
|
+
layer.material
|
280
|
+
for construction in constructions_all
|
281
|
+
for layer in construction.layers
|
282
|
+
if type(layer.material) is GlassMaterial
|
283
|
+
}
|
284
|
+
|
285
|
+
materials_ = [
|
286
|
+
(material.model_dump(exclude={"name"}) | {"id": material.name})
|
287
|
+
for material in materials
|
288
|
+
]
|
289
|
+
gas_ = [
|
290
|
+
(material.model_dump(exclude={"name"}) | {"id": material.name})
|
291
|
+
for material in gas
|
292
|
+
]
|
293
|
+
glass_material = [
|
294
|
+
(_convert_glass(material) | {"id": material.name}) for material in glass_
|
295
|
+
]
|
296
|
+
return {
|
297
|
+
"constructions": constructions,
|
298
|
+
"material": materials_,
|
299
|
+
"glazings": glazings,
|
300
|
+
"gas": gas_,
|
301
|
+
"glass_material": glass_material,
|
302
|
+
}
|
303
|
+
|
304
|
+
|
305
|
+
def _convert_glass(glass_: Material) -> Dict[str, Any]:
|
306
|
+
return {
|
307
|
+
key: (
|
308
|
+
value if not isinstance(value, list) else f"{{{','.join(map(str, value))}}}"
|
309
|
+
)
|
310
|
+
for key, value in glass_.model_dump().items()
|
311
|
+
}
|
ifctrano/exceptions.py
CHANGED
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,11 +1,14 @@
|
|
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
|
6
8
|
import ifcopenshell.util.shape
|
7
9
|
from ifcopenshell import entity_instance, file
|
8
|
-
from pydantic import Field, BeforeValidator, BaseModel
|
10
|
+
from pydantic import Field, BeforeValidator, BaseModel, ConfigDict
|
11
|
+
from shapely import wkt # type: ignore
|
9
12
|
from trano.data_models.conversion import SpaceParameter # type: ignore
|
10
13
|
from trano.elements import Space as TranoSpace, ExternalWall, Window, BaseWall, ExternalDoor # type: ignore
|
11
14
|
from trano.elements.system import Occupancy # type: ignore
|
@@ -33,6 +36,8 @@ from ifctrano.utils import (
|
|
33
36
|
|
34
37
|
ROOF_VECTOR = Vector(x=0, y=0, z=1)
|
35
38
|
|
39
|
+
logger = logging.getLogger(__name__)
|
40
|
+
|
36
41
|
|
37
42
|
def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
|
38
43
|
tree = ifcopenshell.geom.tree()
|
@@ -88,6 +93,14 @@ class Space(GlobalId):
|
|
88
93
|
main_name = f"{remove_non_alphanumeric(self.name)}_" if self.name else ""
|
89
94
|
return f"space_{main_name}{remove_non_alphanumeric(self.entity.GlobalId)}"
|
90
95
|
|
96
|
+
def space_unique_name(self) -> str:
|
97
|
+
base_name = remove_non_alphanumeric(self.name) if self.name else ""
|
98
|
+
main_name = f"{base_name}_" if base_name else ""
|
99
|
+
space_name = f"{main_name}{remove_non_alphanumeric(self.entity.GlobalId)[-3:]}"
|
100
|
+
if "space" not in space_name:
|
101
|
+
return f"space_{space_name}"
|
102
|
+
return space_name
|
103
|
+
|
91
104
|
|
92
105
|
class ExternalSpaceBoundaryGroup(BaseModelConfig):
|
93
106
|
constructions: List[BaseWall]
|
@@ -146,6 +159,10 @@ class ExternalSpaceBoundaryGroups(BaseModelConfig):
|
|
146
159
|
)
|
147
160
|
|
148
161
|
|
162
|
+
def deg_to_rad(deg: float) -> float:
|
163
|
+
return deg * math.pi / 180.0
|
164
|
+
|
165
|
+
|
149
166
|
class Azimuths(BaseModel):
|
150
167
|
north: List[float] = [0.0, 360]
|
151
168
|
east: List[float] = [90.0]
|
@@ -277,6 +294,63 @@ class SpaceBoundaries(BaseShow):
|
|
277
294
|
if space_boundary in self.boundaries:
|
278
295
|
self.boundaries.remove(space_boundary)
|
279
296
|
|
297
|
+
def to_config(
|
298
|
+
self,
|
299
|
+
exclude_entities: List[str],
|
300
|
+
north_axis: Vector,
|
301
|
+
constructions: Constructions,
|
302
|
+
) -> Dict[str, Any]:
|
303
|
+
external_boundaries: Dict[str, Any] = {
|
304
|
+
"external_walls": [],
|
305
|
+
"floor_on_grounds": [],
|
306
|
+
"windows": [],
|
307
|
+
}
|
308
|
+
external_boundaries_check = []
|
309
|
+
for boundary in self.boundaries:
|
310
|
+
boundary_model = boundary.model_element(
|
311
|
+
exclude_entities, north_axis, constructions
|
312
|
+
)
|
313
|
+
if boundary_model:
|
314
|
+
external_boundaries_check.append(boundary_model)
|
315
|
+
element = {
|
316
|
+
"surface": boundary_model.surface,
|
317
|
+
"azimuth": deg_to_rad(boundary_model.azimuth),
|
318
|
+
"tilt": boundary_model.tilt.value,
|
319
|
+
"construction": boundary_model.construction.name,
|
320
|
+
}
|
321
|
+
if isinstance(
|
322
|
+
boundary_model, (ExternalWall, ExternalDoor)
|
323
|
+
) and boundary_model.tilt in [Tilt.wall, Tilt.ceiling]:
|
324
|
+
external_boundaries["external_walls"].append(element)
|
325
|
+
elif isinstance(boundary_model, (Window)):
|
326
|
+
external_boundaries["windows"].append(element)
|
327
|
+
elif isinstance(
|
328
|
+
boundary_model, (ExternalWall)
|
329
|
+
) and boundary_model.tilt in [Tilt.floor]:
|
330
|
+
external_boundaries["floor_on_grounds"].append(element)
|
331
|
+
else:
|
332
|
+
raise ValueError("Unknown boundary type")
|
333
|
+
external_space_boundaries_group = (
|
334
|
+
ExternalSpaceBoundaryGroups.from_external_boundaries(
|
335
|
+
external_boundaries_check
|
336
|
+
)
|
337
|
+
)
|
338
|
+
if not external_space_boundaries_group.has_windows_without_wall():
|
339
|
+
logger.error(
|
340
|
+
f"Space {self.space.global_id} has a boundary that has a windows but without walls."
|
341
|
+
)
|
342
|
+
occupancy_parameters = Occupancy().parameters.model_dump(mode="json")
|
343
|
+
space_parameters = SpaceParameter(
|
344
|
+
floor_area=self.space.floor_area,
|
345
|
+
average_room_height=self.space.average_room_height,
|
346
|
+
).model_dump(mode="json")
|
347
|
+
return {
|
348
|
+
"id": self.space.space_unique_name(),
|
349
|
+
"occupancy": {"parameters": occupancy_parameters},
|
350
|
+
"parameters": space_parameters,
|
351
|
+
"external_boundaries": external_boundaries,
|
352
|
+
}
|
353
|
+
|
280
354
|
def model(
|
281
355
|
self,
|
282
356
|
exclude_entities: List[str],
|
@@ -340,4 +414,109 @@ class SpaceBoundaries(BaseShow):
|
|
340
414
|
)
|
341
415
|
if space_boundary:
|
342
416
|
space_boundaries.append(space_boundary)
|
343
|
-
|
417
|
+
merged_boundaries = MergedSpaceBoundaries.from_boundaries(space_boundaries)
|
418
|
+
space_boundaries_ = merged_boundaries.merge_boundaries_from_part()
|
419
|
+
space_boundaries__ = remove_duplicate_boundaries(space_boundaries_)
|
420
|
+
return cls(space=space_, boundaries=space_boundaries__)
|
421
|
+
|
422
|
+
|
423
|
+
def remove_duplicate_boundaries(
|
424
|
+
boundaries: List[SpaceBoundary],
|
425
|
+
) -> List[SpaceBoundary]:
|
426
|
+
types = ["IfcRoof", "IfcSlab"]
|
427
|
+
boundaries = sorted(boundaries, key=lambda b: b.entity.GlobalId)
|
428
|
+
boundaries_without_types = [
|
429
|
+
sp for sp in boundaries if sp.entity.is_a() not in types
|
430
|
+
]
|
431
|
+
new_boundaries = []
|
432
|
+
for type_ in types:
|
433
|
+
references = [sp for sp in boundaries if sp.entity.is_a() == type_]
|
434
|
+
while True:
|
435
|
+
reference = next(iter(references), None)
|
436
|
+
if not reference:
|
437
|
+
break
|
438
|
+
others = [p_ for p_ in references if p_ != reference]
|
439
|
+
intersecting = [
|
440
|
+
o
|
441
|
+
for o in others
|
442
|
+
if (
|
443
|
+
wkt.loads(o.common_surface.polygon).intersects(
|
444
|
+
wkt.loads(reference.common_surface.polygon)
|
445
|
+
)
|
446
|
+
and o.common_surface.orientation
|
447
|
+
== reference.common_surface.orientation
|
448
|
+
)
|
449
|
+
and (
|
450
|
+
wkt.loads(o.common_surface.polygon).intersection(
|
451
|
+
wkt.loads(reference.common_surface.polygon)
|
452
|
+
)
|
453
|
+
).area
|
454
|
+
> 0
|
455
|
+
]
|
456
|
+
current_group = sorted(
|
457
|
+
[*intersecting, reference], key=lambda p: p.entity.GlobalId
|
458
|
+
)
|
459
|
+
new_boundaries.append(next(iter(current_group)))
|
460
|
+
references = [p_ for p_ in references if p_ not in current_group]
|
461
|
+
return [*boundaries_without_types, *new_boundaries]
|
462
|
+
|
463
|
+
|
464
|
+
class MergedSpaceBoundary(BaseModel):
|
465
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
466
|
+
parent: entity_instance
|
467
|
+
related_boundaries: List[SpaceBoundary]
|
468
|
+
|
469
|
+
def get_new_boundary(self) -> Optional[SpaceBoundary]:
|
470
|
+
related_boundaries = sorted(
|
471
|
+
self.related_boundaries, key=lambda b: b.entity.GlobalId
|
472
|
+
)
|
473
|
+
boundary = next(iter(related_boundaries), None)
|
474
|
+
if boundary:
|
475
|
+
return SpaceBoundary.model_validate(
|
476
|
+
boundary.model_dump() | {"entity": self.parent}
|
477
|
+
)
|
478
|
+
return None
|
479
|
+
|
480
|
+
|
481
|
+
class MergedSpaceBoundaries(BaseModel):
|
482
|
+
part_boundaries: List[MergedSpaceBoundary]
|
483
|
+
original_boundaries: List[SpaceBoundary]
|
484
|
+
|
485
|
+
@classmethod
|
486
|
+
def from_boundaries(
|
487
|
+
cls, space_boundaries: List[SpaceBoundary]
|
488
|
+
) -> "MergedSpaceBoundaries":
|
489
|
+
building_element_part_boundaries = [
|
490
|
+
boundary
|
491
|
+
for boundary in space_boundaries
|
492
|
+
if boundary.entity.is_a() in ["IfcBuildingElementPart"]
|
493
|
+
]
|
494
|
+
existing_parent_entities = {
|
495
|
+
decompose.RelatingObject
|
496
|
+
for b in building_element_part_boundaries
|
497
|
+
for decompose in b.entity.Decomposes
|
498
|
+
}
|
499
|
+
part_boundaries = [
|
500
|
+
MergedSpaceBoundary(
|
501
|
+
parent=parent,
|
502
|
+
related_boundaries=[
|
503
|
+
b
|
504
|
+
for b in building_element_part_boundaries
|
505
|
+
for decompose in b.entity.Decomposes
|
506
|
+
if decompose.RelatingObject == parent
|
507
|
+
],
|
508
|
+
)
|
509
|
+
for parent in existing_parent_entities
|
510
|
+
]
|
511
|
+
return cls(
|
512
|
+
part_boundaries=part_boundaries, original_boundaries=space_boundaries
|
513
|
+
)
|
514
|
+
|
515
|
+
def merge_boundaries_from_part(self) -> List[SpaceBoundary]:
|
516
|
+
new_boundaries = [b.get_new_boundary() for b in self.part_boundaries]
|
517
|
+
new_boundaries_ = [nb for nb in new_boundaries if nb is not None]
|
518
|
+
return [
|
519
|
+
b
|
520
|
+
for b in self.original_boundaries
|
521
|
+
if b.entity.is_a() not in ["IfcBuildingElementPart"]
|
522
|
+
] + new_boundaries_
|
ifctrano/types.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ifctrano
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.0
|
4
4
|
Summary: Package for generating building energy simulation model from IFC
|
5
5
|
License: GPL V3
|
6
|
+
License-File: LICENSE
|
6
7
|
Keywords: BIM,IFC,energy simulation,modelica,building energy simulation,buildings,ideas
|
7
8
|
Author: Ando Andriamamonjy
|
8
9
|
Author-email: andoludovic.andriamamonjy@gmail.com
|
@@ -15,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Requires-Dist: ifcopenshell (>=0.8.1.post1,<0.9.0)
|
16
17
|
Requires-Dist: open3d (>=0.19.0,<0.20.0)
|
17
18
|
Requires-Dist: shapely (>=2.0.7,<3.0.0)
|
18
|
-
Requires-Dist: trano (>=0.
|
19
|
+
Requires-Dist: trano (>=0.10.0,<0.11.0)
|
19
20
|
Requires-Dist: typer (>=0.12.5,<0.13.0)
|
20
21
|
Requires-Dist: vedo (>=2025.5.3,<2026.0.0)
|
21
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=oPRk8Mwvr51sKaWW-sFPPud23a7KAq10-RgpbfFVziw,12074
|
5
|
+
ifctrano/construction.py,sha256=5W9Vo5VyliQ26vl_NTOnR9i5wbZjz6FKL7O3Sm57uNw,9997
|
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=xJc4mBxiJvZMm75APBHMnpNC7xKBZl36BoRjRhTL-xk,19046
|
10
|
+
ifctrano/types.py,sha256=FBSWrD66EL7uHvv0GveCXmYyLtCr8gGRlT7-5j7aZD8,181
|
11
|
+
ifctrano/utils.py,sha256=zLQUGVo15RSQQ-vfOuHsJBkHy4QhpxKSAH7Au72s0s0,873
|
12
|
+
ifctrano-0.8.0.dist-info/METADATA,sha256=cFw_XDKTheAlXSmvnBd4lyhcchKvs17L2fncjFzq8Rs,4884
|
13
|
+
ifctrano-0.8.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
14
|
+
ifctrano-0.8.0.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
|
15
|
+
ifctrano-0.8.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
16
|
+
ifctrano-0.8.0.dist-info/RECORD,,
|
ifctrano-0.4.0.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
ifctrano/__init__.py,sha256=6wK0Qa_5Xe0KSk5Spn98AjTNCTIXtenZ3C0k8ZaVcn8,191
|
2
|
-
ifctrano/base.py,sha256=UNu4LdrgJ20ZUjZ7fuWCAK0GX43vSPrz8WI5Tifyk4k,14335
|
3
|
-
ifctrano/bounding_box.py,sha256=8-XnCmhuh7BwwOHusYWYqTlS_kQhCWHoMysVXg0riZc,7637
|
4
|
-
ifctrano/building.py,sha256=MUaSqWQP_4kZbH6-yrjRahm8poxLBHiBlMjOFNUV88o,8910
|
5
|
-
ifctrano/construction.py,sha256=Lyh_o37znrDyxtKgw7dOqVsQk1S3btCPDh2YTJ8h6T0,7234
|
6
|
-
ifctrano/example/verification.ifc,sha256=tQ9QcubT_wrbb-sc1WRRwYpb2cbkWm3dnRfXdP5GTTg,131
|
7
|
-
ifctrano/exceptions.py,sha256=SKFiFEhn0bW2Dm4fVHeMGIQ1C84nlLhapxy8y46SkfM,488
|
8
|
-
ifctrano/main.py,sha256=YTp7RaWRMKt_UC2DRt_oXXLGTLCAc9FPg6Tsqg1zgpk,4979
|
9
|
-
ifctrano/space_boundary.py,sha256=NvQ-GnS0wT9eBd9XMuO6eoXAejD1uXpjWiPiFP0ro-o,12064
|
10
|
-
ifctrano/types.py,sha256=wxKVb2R4Dz58YzN4PzgXhuuVw-UYobSh9fnYWQrYlqQ,130
|
11
|
-
ifctrano/utils.py,sha256=zLQUGVo15RSQQ-vfOuHsJBkHy4QhpxKSAH7Au72s0s0,873
|
12
|
-
ifctrano-0.4.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
13
|
-
ifctrano-0.4.0.dist-info/METADATA,sha256=LbA7gRHTXt29hRr2CH79-SEZuXTy7J_DU7I18AFixgQ,4860
|
14
|
-
ifctrano-0.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
15
|
-
ifctrano-0.4.0.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
|
16
|
-
ifctrano-0.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|