ifctrano 0.1.7__py3-none-any.whl → 0.1.9__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 +24 -1
- ifctrano/bounding_box.py +33 -11
- ifctrano/building.py +141 -11
- ifctrano/example/verification.ifc +3043 -0
- ifctrano/exceptions.py +16 -0
- ifctrano/main.py +35 -4
- ifctrano/space_boundary.py +133 -32
- {ifctrano-0.1.7.dist-info → ifctrano-0.1.9.dist-info}/METADATA +2 -2
- ifctrano-0.1.9.dist-info/RECORD +13 -0
- ifctrano-0.1.9.dist-info/entry_points.txt +3 -0
- ifctrano-0.1.7.dist-info/RECORD +0 -12
- ifctrano-0.1.7.dist-info/entry_points.txt +0 -3
- {ifctrano-0.1.7.dist-info → ifctrano-0.1.9.dist-info}/LICENSE +0 -0
- {ifctrano-0.1.7.dist-info → ifctrano-0.1.9.dist-info}/WHEEL +0 -0
ifctrano/exceptions.py
CHANGED
@@ -8,3 +8,19 @@ class BoundingBoxFaceError(Exception):
|
|
8
8
|
|
9
9
|
class IfcFileNotFoundError(FileNotFoundError):
|
10
10
|
pass
|
11
|
+
|
12
|
+
|
13
|
+
class SpaceSurfaceAreaNullError(Exception):
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class NoIfcSpaceFoundError(Exception):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class InvalidLibraryError(Exception):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class VectorWithNansError(Exception):
|
26
|
+
pass
|
ifctrano/main.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
import shutil
|
1
2
|
from pathlib import Path
|
2
|
-
from
|
3
|
+
from tempfile import TemporaryDirectory
|
4
|
+
from typing import Annotated, get_args
|
3
5
|
|
4
6
|
import typer
|
5
|
-
from pydantic import validate_call
|
6
7
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
7
8
|
|
8
9
|
from ifctrano.base import Libraries
|
9
10
|
from ifctrano.building import Building
|
11
|
+
from ifctrano.exceptions import InvalidLibraryError
|
10
12
|
|
11
13
|
app = typer.Typer()
|
12
14
|
CHECKMARK = "[green]✔[/green]"
|
@@ -14,14 +16,13 @@ CROSS_MARK = "[red]✘[/red]"
|
|
14
16
|
|
15
17
|
|
16
18
|
@app.command()
|
17
|
-
@validate_call
|
18
19
|
def create(
|
19
20
|
model: Annotated[
|
20
21
|
str,
|
21
22
|
typer.Argument(help="Local path to the ifc file."),
|
22
23
|
],
|
23
24
|
library: Annotated[
|
24
|
-
|
25
|
+
str,
|
25
26
|
typer.Argument(help="Modelica library to be used for simulation."),
|
26
27
|
] = "Buildings",
|
27
28
|
) -> None:
|
@@ -30,6 +31,10 @@ def create(
|
|
30
31
|
TextColumn("[progress.description]{task.description}"),
|
31
32
|
transient=True,
|
32
33
|
) as progress:
|
34
|
+
if library not in get_args(Libraries):
|
35
|
+
raise InvalidLibraryError(
|
36
|
+
f"Invalid library {library}. Valid libraries are {get_args(Libraries)}"
|
37
|
+
)
|
33
38
|
modelica_model_path = Path(model).resolve().with_suffix(".mo")
|
34
39
|
task = progress.add_task(
|
35
40
|
description=f"Generating model {modelica_model_path.name} with library {library} from {model}",
|
@@ -42,3 +47,29 @@ def create(
|
|
42
47
|
modelica_model_path.write_text(modelica_model)
|
43
48
|
progress.remove_task(task)
|
44
49
|
print(f"{CHECKMARK} Model generated at {modelica_model_path}")
|
50
|
+
|
51
|
+
|
52
|
+
@app.command()
|
53
|
+
def verify() -> None:
|
54
|
+
verification_ifc = Path(__file__).parent / "example" / "verification.ifc"
|
55
|
+
with Progress(
|
56
|
+
SpinnerColumn(),
|
57
|
+
TextColumn("[progress.description]{task.description}"),
|
58
|
+
transient=True,
|
59
|
+
) as progress, TemporaryDirectory() as temp_dir:
|
60
|
+
temp_ifc_file = Path(temp_dir) / verification_ifc.name
|
61
|
+
shutil.copy(verification_ifc, temp_ifc_file)
|
62
|
+
task = progress.add_task(
|
63
|
+
description="Trying to create a model from a test file...",
|
64
|
+
total=None,
|
65
|
+
)
|
66
|
+
building = Building.from_ifc(temp_ifc_file)
|
67
|
+
building.save_model()
|
68
|
+
if temp_ifc_file.parent.joinpath(f"{building.name}.mo").exists():
|
69
|
+
progress.remove_task(task)
|
70
|
+
print(f"{CHECKMARK} Model successfully created... your system is ready.")
|
71
|
+
else:
|
72
|
+
progress.remove_task(task)
|
73
|
+
print(
|
74
|
+
f"{CROSS_MARK} Model could not be created... please check your system."
|
75
|
+
)
|
ifctrano/space_boundary.py
CHANGED
@@ -7,17 +7,34 @@ 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.
|
11
|
-
from trano.elements
|
10
|
+
from trano.data_models.conversion import SpaceParameter # type: ignore
|
11
|
+
from trano.elements import Space as TranoSpace, ExternalWall, Window, BaseWall, ExternalDoor # type: ignore
|
12
|
+
from trano.elements.construction import ( # type: ignore
|
13
|
+
Construction,
|
14
|
+
Layer,
|
15
|
+
Material,
|
16
|
+
Glass,
|
17
|
+
GlassLayer,
|
18
|
+
GasLayer,
|
19
|
+
GlassMaterial,
|
20
|
+
Gas,
|
21
|
+
)
|
12
22
|
from trano.elements.system import Occupancy # type: ignore
|
13
|
-
from trano.elements.types import
|
23
|
+
from trano.elements.types import Tilt # type: ignore
|
14
24
|
|
15
|
-
from ifctrano.base import
|
25
|
+
from ifctrano.base import (
|
26
|
+
GlobalId,
|
27
|
+
settings,
|
28
|
+
BaseModelConfig,
|
29
|
+
CommonSurface,
|
30
|
+
ROUNDING_FACTOR,
|
31
|
+
CLASH_CLEARANCE,
|
32
|
+
Vector,
|
33
|
+
)
|
16
34
|
from ifctrano.bounding_box import OrientedBoundingBox
|
17
35
|
|
18
36
|
|
19
37
|
def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
|
20
|
-
|
21
38
|
tree = ifcopenshell.geom.tree()
|
22
39
|
|
23
40
|
iterator = ifcopenshell.geom.iterator(
|
@@ -39,14 +56,41 @@ class Space(GlobalId):
|
|
39
56
|
name: Optional[str] = None
|
40
57
|
bounding_box: OrientedBoundingBox
|
41
58
|
entity: entity_instance
|
59
|
+
average_room_height: float
|
60
|
+
floor_area: float
|
61
|
+
bounding_box_height: float
|
62
|
+
bounding_box_volume: float
|
42
63
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
64
|
+
@classmethod
|
65
|
+
def from_entity(cls, entity: entity_instance) -> "Space":
|
66
|
+
bounding_box = OrientedBoundingBox.from_entity(entity)
|
67
|
+
entity_shape = ifcopenshell.geom.create_shape(settings, entity)
|
68
|
+
area = ifcopenshell.util.shape.get_footprint_area(entity_shape.geometry) # type: ignore
|
69
|
+
volume = ifcopenshell.util.shape.get_volume(entity_shape.geometry) # type: ignore
|
70
|
+
if area:
|
71
|
+
average_room_height = volume / area
|
72
|
+
else:
|
73
|
+
area = bounding_box.volume / bounding_box.height
|
74
|
+
average_room_height = bounding_box.height
|
75
|
+
return cls(
|
76
|
+
global_id=entity.GlobalId,
|
77
|
+
name=entity.Name,
|
78
|
+
bounding_box=bounding_box,
|
79
|
+
entity=entity,
|
80
|
+
average_room_height=average_room_height,
|
81
|
+
floor_area=area,
|
82
|
+
bounding_box_height=bounding_box.height,
|
83
|
+
bounding_box_volume=bounding_box.volume,
|
48
84
|
)
|
49
|
-
|
85
|
+
|
86
|
+
def check_volume(self) -> bool:
|
87
|
+
return round(self.bounding_box_volume, ROUNDING_FACTOR) == round(
|
88
|
+
self.floor_area * self.average_room_height, ROUNDING_FACTOR
|
89
|
+
)
|
90
|
+
|
91
|
+
def space_name(self) -> str:
|
92
|
+
main_name = f"{remove_non_alphanumeric(self.name)}_" if self.name else ""
|
93
|
+
return f"space_{main_name}{remove_non_alphanumeric(self.entity.GlobalId)}"
|
50
94
|
|
51
95
|
|
52
96
|
material_1 = Material(
|
@@ -61,27 +105,81 @@ construction = Construction(
|
|
61
105
|
Layer(material=material_1, thickness=0.18),
|
62
106
|
],
|
63
107
|
)
|
108
|
+
id_100 = GlassMaterial(
|
109
|
+
name="id_100",
|
110
|
+
thermal_conductivity=1,
|
111
|
+
density=2500,
|
112
|
+
specific_heat_capacity=840,
|
113
|
+
solar_transmittance=[0.646],
|
114
|
+
solar_reflectance_outside_facing=[0.062],
|
115
|
+
solar_reflectance_room_facing=[0.063],
|
116
|
+
infrared_transmissivity=0,
|
117
|
+
infrared_absorptivity_outside_facing=0.84,
|
118
|
+
infrared_absorptivity_room_facing=0.84,
|
119
|
+
)
|
120
|
+
|
121
|
+
air = Gas(
|
122
|
+
name="Air",
|
123
|
+
thermal_conductivity=0.025,
|
124
|
+
density=1.2,
|
125
|
+
specific_heat_capacity=1005,
|
126
|
+
)
|
127
|
+
glass = Glass(
|
128
|
+
name="double_glazing",
|
129
|
+
u_value_frame=1.4,
|
130
|
+
layers=[
|
131
|
+
GlassLayer(thickness=0.003, material=id_100),
|
132
|
+
GasLayer(thickness=0.0127, material=air),
|
133
|
+
GlassLayer(thickness=0.003, material=id_100),
|
134
|
+
],
|
135
|
+
)
|
64
136
|
|
65
137
|
|
66
138
|
class SpaceBoundary(BaseModelConfig):
|
67
139
|
bounding_box: OrientedBoundingBox
|
68
140
|
entity: entity_instance
|
69
141
|
common_surface: CommonSurface
|
70
|
-
|
142
|
+
adjacent_spaces: List[Space] = Field(default_factory=list)
|
143
|
+
|
144
|
+
def boundary_name(self) -> str:
|
145
|
+
return f"{self.entity.is_a()}_{remove_non_alphanumeric(self.entity.GlobalId)}"
|
71
146
|
|
72
|
-
def model_element(
|
147
|
+
def model_element(
|
148
|
+
self, exclude_entities: List[str], north_axis: Vector
|
149
|
+
) -> Optional[BaseWall]:
|
150
|
+
if self.entity.GlobalId in exclude_entities:
|
151
|
+
return None
|
152
|
+
azimuth = self.common_surface.orientation.angle(north_axis)
|
73
153
|
if "wall" in self.entity.is_a().lower():
|
74
154
|
return ExternalWall(
|
75
|
-
|
76
|
-
|
155
|
+
name=self.boundary_name(),
|
156
|
+
surface=self.common_surface.area,
|
157
|
+
azimuth=azimuth,
|
158
|
+
tilt=Tilt.wall,
|
159
|
+
construction=construction,
|
160
|
+
)
|
161
|
+
if "door" in self.entity.is_a().lower():
|
162
|
+
return ExternalDoor(
|
163
|
+
name=self.boundary_name(),
|
164
|
+
surface=self.common_surface.area,
|
165
|
+
azimuth=azimuth,
|
77
166
|
tilt=Tilt.wall,
|
78
167
|
construction=construction,
|
79
168
|
)
|
80
169
|
if "window" in self.entity.is_a().lower():
|
81
170
|
return Window(
|
82
|
-
|
83
|
-
|
171
|
+
name=self.boundary_name(),
|
172
|
+
surface=self.common_surface.area,
|
173
|
+
azimuth=azimuth,
|
84
174
|
tilt=Tilt.wall,
|
175
|
+
construction=glass,
|
176
|
+
)
|
177
|
+
if "roof" in self.entity.is_a().lower():
|
178
|
+
return ExternalWall(
|
179
|
+
name=self.boundary_name(),
|
180
|
+
surface=self.common_surface.area,
|
181
|
+
azimuth=azimuth,
|
182
|
+
tilt=Tilt.ceiling,
|
85
183
|
construction=construction,
|
86
184
|
)
|
87
185
|
|
@@ -112,15 +210,24 @@ class SpaceBoundaries(BaseModel):
|
|
112
210
|
space: Space
|
113
211
|
boundaries: List[SpaceBoundary] = Field(default_factory=list)
|
114
212
|
|
115
|
-
def model(
|
213
|
+
def model(
|
214
|
+
self, exclude_entities: List[str], north_axis: Vector
|
215
|
+
) -> Optional[TranoSpace]:
|
216
|
+
external_boundaries = [
|
217
|
+
boundary.model_element(exclude_entities, north_axis)
|
218
|
+
for boundary in self.boundaries
|
219
|
+
if boundary.model_element(exclude_entities, north_axis)
|
220
|
+
]
|
221
|
+
if not external_boundaries:
|
222
|
+
return None
|
116
223
|
return TranoSpace(
|
117
224
|
name=self.space.space_name(),
|
118
225
|
occupancy=Occupancy(),
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
226
|
+
parameters=SpaceParameter(
|
227
|
+
floor_area=self.space.floor_area,
|
228
|
+
average_room_height=self.space.average_room_height,
|
229
|
+
),
|
230
|
+
external_boundaries=external_boundaries,
|
124
231
|
)
|
125
232
|
|
126
233
|
@classmethod
|
@@ -130,13 +237,7 @@ class SpaceBoundaries(BaseModel):
|
|
130
237
|
tree: ifcopenshell.geom.tree,
|
131
238
|
space: entity_instance,
|
132
239
|
) -> "SpaceBoundaries":
|
133
|
-
|
134
|
-
space_ = Space(
|
135
|
-
global_id=space.GlobalId,
|
136
|
-
name=space.Name,
|
137
|
-
bounding_box=bounding_box,
|
138
|
-
entity=space,
|
139
|
-
)
|
240
|
+
space_ = Space.from_entity(space)
|
140
241
|
clashes = tree.clash_clearance_many(
|
141
242
|
[space],
|
142
243
|
ifcopenshell_file.by_type("IfcWall")
|
@@ -144,7 +245,7 @@ class SpaceBoundaries(BaseModel):
|
|
144
245
|
+ ifcopenshell_file.by_type("IfcRoof")
|
145
246
|
+ ifcopenshell_file.by_type("IfcDoor")
|
146
247
|
+ ifcopenshell_file.by_type("IfcWindow"),
|
147
|
-
clearance=
|
248
|
+
clearance=CLASH_CLEARANCE,
|
148
249
|
)
|
149
250
|
space_boundaries = []
|
150
251
|
|
@@ -157,7 +258,7 @@ class SpaceBoundaries(BaseModel):
|
|
157
258
|
if element.GlobalId == space.GlobalId:
|
158
259
|
continue
|
159
260
|
space_boundary = SpaceBoundary.from_space_and_element(
|
160
|
-
bounding_box, element
|
261
|
+
space_.bounding_box, element
|
161
262
|
)
|
162
263
|
if space_boundary:
|
163
264
|
space_boundaries.append(space_boundary)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ifctrano
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.9
|
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,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
22
22
|
|
23
23
|
# ifctrano - IFC to Energy Simulation Tool
|
24
24
|
|
25
|
-
📖 **Full Documentation:** 👉 [
|
25
|
+
📖 **Full Documentation:** 👉 [ifctrano Docs](https://andoludo.github.io/ifctrano/)
|
26
26
|
|
27
27
|
```bash
|
28
28
|
pip install ifctrano
|
@@ -0,0 +1,13 @@
|
|
1
|
+
ifctrano/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
ifctrano/base.py,sha256=xpfZ27dZ5SZ5z2fz3N4GonkGWvXRYhMfOjoy94R4Yt8,5748
|
3
|
+
ifctrano/bounding_box.py,sha256=OnkOR3NOyVRFSdo0d9BeFm93ZU40TV_gQSxYHOEPCYE,9382
|
4
|
+
ifctrano/building.py,sha256=NoF0V5H_jDoyl0zkWPrSQa4xX0HIA23Kqu_TbH4Q56M,7134
|
5
|
+
ifctrano/example/verification.ifc,sha256=L6YRD_rny7IEB4myA7uCLO6eI-xXOn2jp_67lw3lUrE,128600
|
6
|
+
ifctrano/exceptions.py,sha256=1FQeuuq_lVZ4CawwewQvkE8OlDQwhBTbKfmc2FiZodo,431
|
7
|
+
ifctrano/main.py,sha256=5S2K0dvFd5k15nsomeDmLiIxFlqYdFr2hdepDFqf0Fs,2672
|
8
|
+
ifctrano/space_boundary.py,sha256=SxVXkBi8TNZfmPDFT-6epuewk8E9V1astVgcD3FT8ew,8617
|
9
|
+
ifctrano-0.1.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
10
|
+
ifctrano-0.1.9.dist-info/METADATA,sha256=EJUPwFbIeYTFV7Jo0nNRF4Vppzu0ihJg7eB3_KFYuF0,3003
|
11
|
+
ifctrano-0.1.9.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
12
|
+
ifctrano-0.1.9.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
|
13
|
+
ifctrano-0.1.9.dist-info/RECORD,,
|
ifctrano-0.1.7.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
ifctrano/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
ifctrano/base.py,sha256=vwWmzOTnMpE5rmnIti7QeG92Z_rpk-9nIrl7I9Or1cA,4971
|
3
|
-
ifctrano/bounding_box.py,sha256=_cvS6G6RaNKCFIE3gaQO-bQTKOxIvn0xDZ4rKSCL5T4,8390
|
4
|
-
ifctrano/building.py,sha256=0RCxlupyPl7c7d1F-JVL0mjDGpB7MPYVpoDuXFI0AeE,1940
|
5
|
-
ifctrano/exceptions.py,sha256=_kntVIsqEvz3zxtJd8RougQRXEwjF5avarmGB-mig1w,228
|
6
|
-
ifctrano/main.py,sha256=97Uh8JgjoeeeZ3VMP1_STpDH23mOlW2M9oo9gwECLPE,1411
|
7
|
-
ifctrano/space_boundary.py,sha256=PkBiLPT26-Us7emyjqNUjKDL4GWMxfQQnHYOgRhelEI,5175
|
8
|
-
ifctrano-0.1.7.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
9
|
-
ifctrano-0.1.7.dist-info/METADATA,sha256=1kgy06CAiqdoqALp5VPMAO9qSULiB1ecI4Su6NFd9Dc,3000
|
10
|
-
ifctrano-0.1.7.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
11
|
-
ifctrano-0.1.7.dist-info/entry_points.txt,sha256=lHxD6pcOuPxITBoWjE35s4f6Dy7tzPa48ffuI-ehb2M,43
|
12
|
-
ifctrano-0.1.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|