ifctrano 0.1.6__tar.gz → 0.1.8__tar.gz
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-0.1.6 → ifctrano-0.1.8}/PKG-INFO +7 -1
- {ifctrano-0.1.6 → ifctrano-0.1.8}/README.md +6 -0
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/base.py +1 -0
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/bounding_box.py +24 -9
- ifctrano-0.1.8/ifctrano/building.py +133 -0
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/exceptions.py +8 -0
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/main.py +7 -4
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/space_boundary.py +61 -17
- {ifctrano-0.1.6 → ifctrano-0.1.8}/pyproject.toml +2 -2
- ifctrano-0.1.6/ifctrano/building.py +0 -55
- {ifctrano-0.1.6 → ifctrano-0.1.8}/LICENSE +0 -0
- {ifctrano-0.1.6 → ifctrano-0.1.8}/ifctrano/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ifctrano
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
4
4
|
Summary: Package for generating building energy simulation model from IFC
|
5
5
|
Home-page: https://github.com/andoludo/ifctrano
|
6
6
|
License: GPL V3
|
@@ -22,6 +22,12 @@ Description-Content-Type: text/markdown
|
|
22
22
|
|
23
23
|
# ifctrano - IFC to Energy Simulation Tool
|
24
24
|
|
25
|
+
📖 **Full Documentation:** 👉 [Trano Docs](https://andoludo.github.io/ifctrano/)
|
26
|
+
|
27
|
+
```bash
|
28
|
+
pip install ifctrano
|
29
|
+
```
|
30
|
+
|
25
31
|
## Overview
|
26
32
|
ifctrano is yet another **IFC to energy simulation** tool designed to translate **Industry Foundation Classes (IFC)** models into energy simulation models in **Modelica**.
|
27
33
|
|
@@ -1,5 +1,11 @@
|
|
1
1
|
# ifctrano - IFC to Energy Simulation Tool
|
2
2
|
|
3
|
+
📖 **Full Documentation:** 👉 [Trano Docs](https://andoludo.github.io/ifctrano/)
|
4
|
+
|
5
|
+
```bash
|
6
|
+
pip install ifctrano
|
7
|
+
```
|
8
|
+
|
3
9
|
## Overview
|
4
10
|
ifctrano is yet another **IFC to energy simulation** tool designed to translate **Industry Foundation Classes (IFC)** models into energy simulation models in **Modelica**.
|
5
11
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from logging import getLogger
|
2
|
-
from typing import List, Optional, Any, Tuple
|
2
|
+
from typing import List, Optional, Any, Tuple, cast
|
3
3
|
|
4
4
|
import ifcopenshell
|
5
5
|
import numpy as np
|
@@ -23,6 +23,7 @@ from ifctrano.base import (
|
|
23
23
|
settings,
|
24
24
|
CommonSurface,
|
25
25
|
AREA_TOLERANCE,
|
26
|
+
ROUNDING_FACTOR,
|
26
27
|
)
|
27
28
|
from ifctrano.exceptions import BoundingBoxFaceError
|
28
29
|
|
@@ -77,7 +78,12 @@ class BoundingBoxFace(BaseModelConfig):
|
|
77
78
|
vertices=vertices, normal=normal, coordinate_system=coordinate_system
|
78
79
|
)
|
79
80
|
|
81
|
+
def get_face_area(self) -> float:
|
82
|
+
polygon_2d = self.get_2d_polygon(self.coordinate_system)
|
83
|
+
return cast(float, round(polygon_2d.polygon.area, ROUNDING_FACTOR))
|
84
|
+
|
80
85
|
def get_2d_polygon(self, coordinate_system: CoordinateSystem) -> Polygon2D:
|
86
|
+
|
81
87
|
projected_vertices = coordinate_system.inverse(self.vertices.to_array())
|
82
88
|
projected_normal_index = Vector.from_array(
|
83
89
|
coordinate_system.inverse(self.normal.to_array())
|
@@ -152,6 +158,8 @@ class OrientedBoundingBox(BaseModel):
|
|
152
158
|
faces: BoundingBoxFaces
|
153
159
|
centroid: Point
|
154
160
|
area_tolerance: float = Field(default=AREA_TOLERANCE)
|
161
|
+
volume: float
|
162
|
+
height: float
|
155
163
|
|
156
164
|
def intersect_faces(self, other: "OrientedBoundingBox") -> Optional[CommonSurface]:
|
157
165
|
extend_surfaces = []
|
@@ -160,11 +168,9 @@ class OrientedBoundingBox(BaseModel):
|
|
160
168
|
for other_face in other.faces.faces:
|
161
169
|
vector = face.normal * other_face.normal
|
162
170
|
if vector.is_a_zero():
|
163
|
-
|
164
171
|
polygon_1 = other_face.get_2d_polygon(face.coordinate_system)
|
165
172
|
polygon_2 = face.get_2d_polygon(face.coordinate_system)
|
166
173
|
intersection = polygon_2.polygon.intersection(polygon_1.polygon)
|
167
|
-
|
168
174
|
if intersection.area > self.area_tolerance:
|
169
175
|
distance = abs(polygon_1.length - polygon_2.length)
|
170
176
|
area = intersection.area
|
@@ -194,8 +200,8 @@ class OrientedBoundingBox(BaseModel):
|
|
194
200
|
) -> "OrientedBoundingBox":
|
195
201
|
vertices_np = np.array(vertices)
|
196
202
|
points = np.asarray(vertices_np)
|
197
|
-
cov = np.cov(points, y=None, rowvar=0, bias=
|
198
|
-
v, vect = np.linalg.eig(cov)
|
203
|
+
cov = np.cov(points, y=None, rowvar=0, bias=0) # type: ignore
|
204
|
+
v, vect = np.linalg.eig(np.round(cov, ROUNDING_FACTOR))
|
199
205
|
tvect = np.transpose(vect)
|
200
206
|
points_r = np.dot(points, np.linalg.inv(tvect))
|
201
207
|
|
@@ -206,9 +212,12 @@ class OrientedBoundingBox(BaseModel):
|
|
206
212
|
ymin, ymax = co_min[1], co_max[1]
|
207
213
|
zmin, zmax = co_min[2], co_max[2]
|
208
214
|
|
209
|
-
|
210
|
-
|
211
|
-
|
215
|
+
x_len = xmax - xmin
|
216
|
+
y_len = ymax - ymin
|
217
|
+
z_len = zmax - zmin
|
218
|
+
xdif = x_len * 0.5
|
219
|
+
ydif = y_len * 0.5
|
220
|
+
zdif = z_len * 0.5
|
212
221
|
|
213
222
|
cx = xmin + xdif
|
214
223
|
cy = ymin + ydif
|
@@ -225,7 +234,11 @@ class OrientedBoundingBox(BaseModel):
|
|
225
234
|
[cx + xdif, cy - ydif, cz - zdif],
|
226
235
|
]
|
227
236
|
)
|
228
|
-
|
237
|
+
corners_ = np.dot(corners, tvect)
|
238
|
+
dims = np.transpose(corners_)
|
239
|
+
x_size = np.max(dims[0]) - np.min(dims[0])
|
240
|
+
y_size = np.max(dims[1]) - np.min(dims[1])
|
241
|
+
z_size = np.max(dims[2]) - np.min(dims[2])
|
229
242
|
coordinate_system = CoordinateSystem.from_array(tvect)
|
230
243
|
c = P(x=cx, y=cy, z=cz)
|
231
244
|
d = P(x=xdif, y=ydif, z=zdif)
|
@@ -233,6 +246,8 @@ class OrientedBoundingBox(BaseModel):
|
|
233
246
|
return cls(
|
234
247
|
faces=faces,
|
235
248
|
centroid=Point.from_array(coordinate_system.project(c.to_array())),
|
249
|
+
volume=x_size * y_size * z_size,
|
250
|
+
height=z_size,
|
236
251
|
)
|
237
252
|
|
238
253
|
@classmethod
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
import ifcopenshell
|
5
|
+
from ifcopenshell import file, entity_instance
|
6
|
+
from pydantic import validate_call, Field, model_validator
|
7
|
+
from trano.elements import InternalElement # type: ignore
|
8
|
+
from trano.elements.library.library import Library # type: ignore
|
9
|
+
from trano.elements.types import Tilt # type: ignore
|
10
|
+
from trano.topology import Network # type: ignore
|
11
|
+
|
12
|
+
from ifctrano.base import BaseModelConfig, Libraries
|
13
|
+
from ifctrano.exceptions import IfcFileNotFoundError
|
14
|
+
from ifctrano.space_boundary import (
|
15
|
+
SpaceBoundaries,
|
16
|
+
initialize_tree,
|
17
|
+
Space,
|
18
|
+
construction,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
def get_spaces(ifcopenshell_file: file) -> List[entity_instance]:
|
23
|
+
return ifcopenshell_file.by_type("IfcSpace")
|
24
|
+
|
25
|
+
|
26
|
+
class IfcInternalElement(BaseModelConfig):
|
27
|
+
spaces: List[Space]
|
28
|
+
element: entity_instance
|
29
|
+
area: float
|
30
|
+
|
31
|
+
|
32
|
+
class InternalElements(BaseModelConfig):
|
33
|
+
elements: List[IfcInternalElement] = Field(default_factory=list)
|
34
|
+
|
35
|
+
def internal_element_ids(self) -> List[str]:
|
36
|
+
return list({e.element.GlobalId for e in self.elements})
|
37
|
+
|
38
|
+
|
39
|
+
class Building(BaseModelConfig):
|
40
|
+
name: str
|
41
|
+
space_boundaries: List[SpaceBoundaries]
|
42
|
+
ifc_file: file
|
43
|
+
parent_folder: Path
|
44
|
+
internal_elements: InternalElements = Field(default_factory=InternalElements)
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def from_ifc(cls, ifc_file_path: Path) -> "Building":
|
48
|
+
if not ifc_file_path.exists():
|
49
|
+
raise IfcFileNotFoundError(
|
50
|
+
f"File specified {ifc_file_path} does not exist."
|
51
|
+
)
|
52
|
+
ifc_file = ifcopenshell.open(str(ifc_file_path))
|
53
|
+
tree = initialize_tree(ifc_file)
|
54
|
+
spaces = get_spaces(ifc_file)
|
55
|
+
space_boundaries = [
|
56
|
+
SpaceBoundaries.from_space_entity(ifc_file, tree, space) for space in spaces
|
57
|
+
]
|
58
|
+
return cls(
|
59
|
+
space_boundaries=space_boundaries,
|
60
|
+
ifc_file=ifc_file,
|
61
|
+
parent_folder=ifc_file_path.parent,
|
62
|
+
name=ifc_file_path.stem,
|
63
|
+
)
|
64
|
+
|
65
|
+
@model_validator(mode="after")
|
66
|
+
def _validator(self) -> "Building":
|
67
|
+
self.internal_elements = self.get_adjacency()
|
68
|
+
return self
|
69
|
+
|
70
|
+
def get_adjacency(self) -> InternalElements:
|
71
|
+
elements = []
|
72
|
+
for space_boundaries_ in self.space_boundaries:
|
73
|
+
for space_boundaries__ in self.space_boundaries:
|
74
|
+
space_1 = space_boundaries_.space
|
75
|
+
space_2 = space_boundaries__.space
|
76
|
+
if space_1.global_id == space_2.global_id:
|
77
|
+
continue
|
78
|
+
common_surface = space_1.bounding_box.intersect_faces(
|
79
|
+
space_2.bounding_box
|
80
|
+
)
|
81
|
+
for boundary in space_boundaries_.boundaries:
|
82
|
+
for boundary_ in space_boundaries__.boundaries:
|
83
|
+
if (
|
84
|
+
boundary.entity.GlobalId == boundary_.entity.GlobalId
|
85
|
+
and boundary.common_surface
|
86
|
+
and boundary_.common_surface
|
87
|
+
and common_surface
|
88
|
+
and (
|
89
|
+
boundary.common_surface.orientation
|
90
|
+
* common_surface.orientation
|
91
|
+
).is_a_zero()
|
92
|
+
and (
|
93
|
+
boundary_.common_surface.orientation
|
94
|
+
* common_surface.orientation
|
95
|
+
).is_a_zero()
|
96
|
+
):
|
97
|
+
elements.append( # noqa: PERF401
|
98
|
+
IfcInternalElement(
|
99
|
+
spaces=[space_1, space_2],
|
100
|
+
element=boundary_.entity,
|
101
|
+
area=common_surface.area,
|
102
|
+
)
|
103
|
+
)
|
104
|
+
return InternalElements(elements=elements)
|
105
|
+
|
106
|
+
@validate_call
|
107
|
+
def create_model(self, library: Libraries = "Buildings") -> str:
|
108
|
+
network = Network(name=self.name, library=Library.from_configuration(library))
|
109
|
+
spaces = {
|
110
|
+
space_boundary.space.global_id: space_boundary.model(
|
111
|
+
self.internal_elements.internal_element_ids()
|
112
|
+
)
|
113
|
+
for space_boundary in self.space_boundaries
|
114
|
+
}
|
115
|
+
network.add_boiler_plate_spaces(list(spaces.values()))
|
116
|
+
for internal_element in self.internal_elements.elements:
|
117
|
+
space_1 = internal_element.spaces[0]
|
118
|
+
space_2 = internal_element.spaces[1]
|
119
|
+
network.connect_spaces(
|
120
|
+
spaces[space_1.global_id],
|
121
|
+
spaces[space_2.global_id],
|
122
|
+
InternalElement(
|
123
|
+
azimuth=10,
|
124
|
+
construction=construction,
|
125
|
+
surface=internal_element.area,
|
126
|
+
tilt=Tilt.wall,
|
127
|
+
),
|
128
|
+
)
|
129
|
+
return network.model() # type: ignore
|
130
|
+
|
131
|
+
def save_model(self, library: Libraries = "Buildings") -> None:
|
132
|
+
model_ = self.create_model(library)
|
133
|
+
Path(self.parent_folder.joinpath(f"{self.name}.mo")).write_text(model_)
|
@@ -1,12 +1,12 @@
|
|
1
1
|
from pathlib import Path
|
2
|
-
from typing import Annotated
|
2
|
+
from typing import Annotated, get_args
|
3
3
|
|
4
4
|
import typer
|
5
|
-
from pydantic import validate_call
|
6
5
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
7
6
|
|
8
7
|
from ifctrano.base import Libraries
|
9
8
|
from ifctrano.building import Building
|
9
|
+
from ifctrano.exceptions import InvalidLibraryError
|
10
10
|
|
11
11
|
app = typer.Typer()
|
12
12
|
CHECKMARK = "[green]✔[/green]"
|
@@ -14,14 +14,13 @@ CROSS_MARK = "[red]✘[/red]"
|
|
14
14
|
|
15
15
|
|
16
16
|
@app.command()
|
17
|
-
@validate_call
|
18
17
|
def create(
|
19
18
|
model: Annotated[
|
20
19
|
str,
|
21
20
|
typer.Argument(help="Local path to the ifc file."),
|
22
21
|
],
|
23
22
|
library: Annotated[
|
24
|
-
|
23
|
+
str,
|
25
24
|
typer.Argument(help="Modelica library to be used for simulation."),
|
26
25
|
] = "Buildings",
|
27
26
|
) -> None:
|
@@ -30,6 +29,10 @@ def create(
|
|
30
29
|
TextColumn("[progress.description]{task.description}"),
|
31
30
|
transient=True,
|
32
31
|
) as progress:
|
32
|
+
if library not in get_args(Libraries):
|
33
|
+
raise InvalidLibraryError(
|
34
|
+
f"Invalid library {library}. Valid libraries are {get_args(Libraries)}"
|
35
|
+
)
|
33
36
|
modelica_model_path = Path(model).resolve().with_suffix(".mo")
|
34
37
|
task = progress.add_task(
|
35
38
|
description=f"Generating model {modelica_model_path.name} with library {library} from {model}",
|
@@ -7,17 +7,23 @@ import ifcopenshell.geom
|
|
7
7
|
import ifcopenshell.util.shape
|
8
8
|
from ifcopenshell import entity_instance, file
|
9
9
|
from pydantic import BaseModel, Field
|
10
|
+
from trano.data_models.conversion import SpaceParameter # type: ignore
|
10
11
|
from trano.elements import Space as TranoSpace, ExternalWall, Window, BaseWall # type: ignore
|
11
12
|
from trano.elements.construction import Construction, Layer, Material # type: ignore
|
12
13
|
from trano.elements.system import Occupancy # type: ignore
|
13
14
|
from trano.elements.types import Azimuth, Tilt # type: ignore
|
14
15
|
|
15
|
-
from ifctrano.base import
|
16
|
+
from ifctrano.base import (
|
17
|
+
GlobalId,
|
18
|
+
settings,
|
19
|
+
BaseModelConfig,
|
20
|
+
CommonSurface,
|
21
|
+
ROUNDING_FACTOR,
|
22
|
+
)
|
16
23
|
from ifctrano.bounding_box import OrientedBoundingBox
|
17
24
|
|
18
25
|
|
19
26
|
def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
|
20
|
-
|
21
27
|
tree = ifcopenshell.geom.tree()
|
22
28
|
|
23
29
|
iterator = ifcopenshell.geom.iterator(
|
@@ -39,6 +45,37 @@ class Space(GlobalId):
|
|
39
45
|
name: Optional[str] = None
|
40
46
|
bounding_box: OrientedBoundingBox
|
41
47
|
entity: entity_instance
|
48
|
+
average_room_height: float
|
49
|
+
floor_area: float
|
50
|
+
bounding_box_height: float
|
51
|
+
bounding_box_volume: float
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def from_entity(cls, entity: entity_instance) -> "Space":
|
55
|
+
bounding_box = OrientedBoundingBox.from_entity(entity)
|
56
|
+
entity_shape = ifcopenshell.geom.create_shape(settings, entity)
|
57
|
+
area = ifcopenshell.util.shape.get_footprint_area(entity_shape.geometry) # type: ignore
|
58
|
+
volume = ifcopenshell.util.shape.get_volume(entity_shape.geometry) # type: ignore
|
59
|
+
if area:
|
60
|
+
average_room_height = volume / area
|
61
|
+
else:
|
62
|
+
area = bounding_box.volume / bounding_box.height
|
63
|
+
average_room_height = bounding_box.height
|
64
|
+
return cls(
|
65
|
+
global_id=entity.GlobalId,
|
66
|
+
name=entity.Name,
|
67
|
+
bounding_box=bounding_box,
|
68
|
+
entity=entity,
|
69
|
+
average_room_height=average_room_height,
|
70
|
+
floor_area=area,
|
71
|
+
bounding_box_height=bounding_box.height,
|
72
|
+
bounding_box_volume=bounding_box.volume,
|
73
|
+
)
|
74
|
+
|
75
|
+
def check_volume(self) -> bool:
|
76
|
+
return round(self.bounding_box_volume, ROUNDING_FACTOR) == round(
|
77
|
+
self.floor_area * self.average_room_height, ROUNDING_FACTOR
|
78
|
+
)
|
42
79
|
|
43
80
|
def space_name(self) -> str:
|
44
81
|
main_name = (
|
@@ -67,23 +104,32 @@ class SpaceBoundary(BaseModelConfig):
|
|
67
104
|
bounding_box: OrientedBoundingBox
|
68
105
|
entity: entity_instance
|
69
106
|
common_surface: CommonSurface
|
70
|
-
|
107
|
+
adjacent_spaces: List[Space] = Field(default_factory=list)
|
71
108
|
|
72
|
-
def model_element(self) -> Optional[BaseWall]:
|
109
|
+
def model_element(self, exclude_entities: List[str]) -> Optional[BaseWall]:
|
110
|
+
if self.entity.GlobalId in exclude_entities:
|
111
|
+
return None
|
73
112
|
if "wall" in self.entity.is_a().lower():
|
74
113
|
return ExternalWall(
|
75
|
-
surface=
|
114
|
+
surface=self.common_surface.area,
|
76
115
|
azimuth=Azimuth.south,
|
77
116
|
tilt=Tilt.wall,
|
78
117
|
construction=construction,
|
79
118
|
)
|
80
119
|
if "window" in self.entity.is_a().lower():
|
81
120
|
return Window(
|
82
|
-
surface=
|
121
|
+
surface=self.common_surface.area,
|
83
122
|
azimuth=Azimuth.south,
|
84
123
|
tilt=Tilt.wall,
|
85
124
|
construction=construction,
|
86
125
|
)
|
126
|
+
if "roof" in self.entity.is_a().lower():
|
127
|
+
return ExternalWall(
|
128
|
+
surface=self.common_surface.area,
|
129
|
+
azimuth=Azimuth.south,
|
130
|
+
tilt=Tilt.ceiling,
|
131
|
+
construction=construction,
|
132
|
+
)
|
87
133
|
|
88
134
|
return None
|
89
135
|
|
@@ -112,14 +158,18 @@ class SpaceBoundaries(BaseModel):
|
|
112
158
|
space: Space
|
113
159
|
boundaries: List[SpaceBoundary] = Field(default_factory=list)
|
114
160
|
|
115
|
-
def model(self) -> TranoSpace:
|
161
|
+
def model(self, exclude_entities: List[str]) -> TranoSpace:
|
116
162
|
return TranoSpace(
|
117
163
|
name=self.space.space_name(),
|
118
164
|
occupancy=Occupancy(),
|
165
|
+
parameters=SpaceParameter(
|
166
|
+
floor_area=self.space.floor_area,
|
167
|
+
average_room_height=self.space.average_room_height,
|
168
|
+
),
|
119
169
|
external_boundaries=[
|
120
|
-
boundary.model_element()
|
170
|
+
boundary.model_element(exclude_entities)
|
121
171
|
for boundary in self.boundaries
|
122
|
-
if boundary.model_element()
|
172
|
+
if boundary.model_element(exclude_entities)
|
123
173
|
],
|
124
174
|
)
|
125
175
|
|
@@ -130,13 +180,7 @@ class SpaceBoundaries(BaseModel):
|
|
130
180
|
tree: ifcopenshell.geom.tree,
|
131
181
|
space: entity_instance,
|
132
182
|
) -> "SpaceBoundaries":
|
133
|
-
|
134
|
-
space_ = Space(
|
135
|
-
global_id=space.GlobalId,
|
136
|
-
name=space.Name,
|
137
|
-
bounding_box=bounding_box,
|
138
|
-
entity=space,
|
139
|
-
)
|
183
|
+
space_ = Space.from_entity(space)
|
140
184
|
clashes = tree.clash_clearance_many(
|
141
185
|
[space],
|
142
186
|
ifcopenshell_file.by_type("IfcWall")
|
@@ -157,7 +201,7 @@ class SpaceBoundaries(BaseModel):
|
|
157
201
|
if element.GlobalId == space.GlobalId:
|
158
202
|
continue
|
159
203
|
space_boundary = SpaceBoundary.from_space_and_element(
|
160
|
-
bounding_box, element
|
204
|
+
space_.bounding_box, element
|
161
205
|
)
|
162
206
|
if space_boundary:
|
163
207
|
space_boundaries.append(space_boundary)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "ifctrano"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.8"
|
4
4
|
description = "Package for generating building energy simulation model from IFC"
|
5
5
|
authors = ["Ando Andriamamonjy <andoludovic.andriamamonjy@gmail.com>"]
|
6
6
|
license = "GPL V3"
|
@@ -34,7 +34,7 @@ black = "^24.10.0"
|
|
34
34
|
pytest = "^7.4.3"
|
35
35
|
|
36
36
|
[tool.poetry.scripts]
|
37
|
-
|
37
|
+
ifctrano = "ifctrano.main:app"
|
38
38
|
|
39
39
|
|
40
40
|
[tool.poetry.group.docs.dependencies]
|
@@ -1,55 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from typing import List
|
3
|
-
|
4
|
-
import ifcopenshell
|
5
|
-
from ifcopenshell import file, entity_instance
|
6
|
-
from pydantic import validate_call
|
7
|
-
from trano.elements.library.library import Library # type: ignore
|
8
|
-
|
9
|
-
from trano.topology import Network # type: ignore
|
10
|
-
|
11
|
-
from ifctrano.base import BaseModelConfig, Libraries
|
12
|
-
from ifctrano.exceptions import IfcFileNotFoundError
|
13
|
-
from ifctrano.space_boundary import SpaceBoundaries, initialize_tree
|
14
|
-
|
15
|
-
|
16
|
-
def get_spaces(ifcopenshell_file: file) -> List[entity_instance]:
|
17
|
-
return ifcopenshell_file.by_type("IfcSpace")
|
18
|
-
|
19
|
-
|
20
|
-
class Building(BaseModelConfig):
|
21
|
-
name: str
|
22
|
-
space_boundaries: List[SpaceBoundaries]
|
23
|
-
ifc_file: file
|
24
|
-
parent_folder: Path
|
25
|
-
|
26
|
-
@classmethod
|
27
|
-
def from_ifc(cls, ifc_file_path: Path) -> "Building":
|
28
|
-
if not ifc_file_path.exists():
|
29
|
-
raise IfcFileNotFoundError(
|
30
|
-
f"File specified {ifc_file_path} does not exist."
|
31
|
-
)
|
32
|
-
ifc_file = ifcopenshell.open(str(ifc_file_path))
|
33
|
-
tree = initialize_tree(ifc_file)
|
34
|
-
spaces = get_spaces(ifc_file)
|
35
|
-
space_boundaries = [
|
36
|
-
SpaceBoundaries.from_space_entity(ifc_file, tree, space) for space in spaces
|
37
|
-
]
|
38
|
-
return cls(
|
39
|
-
space_boundaries=space_boundaries,
|
40
|
-
ifc_file=ifc_file,
|
41
|
-
parent_folder=ifc_file_path.parent,
|
42
|
-
name=ifc_file_path.stem,
|
43
|
-
)
|
44
|
-
|
45
|
-
@validate_call
|
46
|
-
def create_model(self, library: Libraries = "Buildings") -> str:
|
47
|
-
network = Network(name=self.name, library=Library.from_configuration(library))
|
48
|
-
network.add_boiler_plate_spaces(
|
49
|
-
[space_boundary.model() for space_boundary in self.space_boundaries]
|
50
|
-
)
|
51
|
-
return network.model() # type: ignore
|
52
|
-
|
53
|
-
def save_model(self, library: Libraries = "Buildings") -> None:
|
54
|
-
model_ = self.create_model(library)
|
55
|
-
Path(self.parent_folder.joinpath(f"{self.name}.mo")).write_text(model_)
|
File without changes
|
File without changes
|