ifctrano 0.1.12__py3-none-any.whl → 0.2.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/main.py CHANGED
@@ -1,10 +1,16 @@
1
1
  import shutil
2
+ import webbrowser
2
3
  from pathlib import Path
3
4
  from tempfile import TemporaryDirectory
4
5
  from typing import Annotated, get_args
5
6
 
6
7
  import typer
7
8
  from rich.progress import Progress, SpinnerColumn, TextColumn
9
+ from trano.reporting.html import to_html_reporting # type: ignore
10
+ from trano.reporting.reporting import ModelDocumentation # type: ignore
11
+ from trano.reporting.types import ResultFile # type: ignore
12
+ from trano.simulate.simulate import simulate # type: ignore
13
+ from trano.utils.utils import is_success # type: ignore
8
14
 
9
15
  from ifctrano.base import Libraries
10
16
  from ifctrano.building import Building
@@ -25,6 +31,14 @@ def create(
25
31
  str,
26
32
  typer.Argument(help="Modelica library to be used for simulation."),
27
33
  ] = "Buildings",
34
+ show_space_boundaries: Annotated[
35
+ bool,
36
+ typer.Option(help="Show computed space boundaries."),
37
+ ] = True,
38
+ simulate_model: Annotated[
39
+ bool,
40
+ typer.Option(help="Simulate the generated model."),
41
+ ] = True,
28
42
  ) -> None:
29
43
  with Progress(
30
44
  SpinnerColumn(),
@@ -41,12 +55,47 @@ def create(
41
55
  total=None,
42
56
  )
43
57
  building = Building.from_ifc(Path(model))
44
- modelica_model = building.create_model(library=library) # type: ignore
58
+ if show_space_boundaries:
59
+ print(f"{CHECKMARK} Showing space boundaries.")
60
+ building.show()
61
+ modelica_network = building.create_model(library=library) # type: ignore
45
62
  progress.update(task, completed=True)
46
63
  task = progress.add_task(description="Writing model to file...", total=None)
47
- modelica_model_path.write_text(modelica_model)
64
+ modelica_model_path.write_text(modelica_network.model())
48
65
  progress.remove_task(task)
49
66
  print(f"{CHECKMARK} Model generated at {modelica_model_path}")
67
+ if simulate_model:
68
+ print("Simulating...")
69
+ results = simulate(
70
+ modelica_model_path.parent,
71
+ building.create_model(
72
+ library=library # type: ignore
73
+ ), # TODO: cannot use the network after cretingt he model
74
+ )
75
+ if not is_success(results):
76
+ print(f"{CROSS_MARK} Simulation failed. See logs for more information.")
77
+ return
78
+
79
+ result_path = (
80
+ Path(modelica_model_path.parent)
81
+ / "results"
82
+ / f"{modelica_model_path.stem}.building_res.mat"
83
+ )
84
+ if not result_path.exists():
85
+ print(
86
+ f"{CROSS_MARK} Simulation failed. Result file not found in {result_path}."
87
+ )
88
+ return
89
+ reporting = ModelDocumentation.from_network(
90
+ building.create_model(library=library), # type: ignore
91
+ result=ResultFile(path=result_path),
92
+ )
93
+ html = to_html_reporting(reporting)
94
+ report_path = Path(
95
+ modelica_model_path.parent / f"{modelica_model_path.stem}.html"
96
+ )
97
+ report_path.write_text(html)
98
+ webbrowser.open(f"file://{report_path}")
50
99
 
51
100
 
52
101
  @app.command()
@@ -73,3 +122,7 @@ def verify() -> None:
73
122
  print(
74
123
  f"{CROSS_MARK} Model could not be created... please check your system."
75
124
  )
125
+
126
+
127
+ if __name__ == "__main__":
128
+ app()
@@ -1,37 +1,31 @@
1
1
  import multiprocessing
2
- import re
3
- from typing import Optional, List, Tuple, Any
2
+ from typing import Optional, List, Tuple, Any, Annotated
4
3
 
5
4
  import ifcopenshell
6
5
  import ifcopenshell.geom
7
6
  import ifcopenshell.util.shape
8
7
  from ifcopenshell import entity_instance, file
9
- from pydantic import BaseModel, Field
8
+ from pydantic import Field, BeforeValidator
10
9
  from trano.data_models.conversion import SpaceParameter # type: ignore
11
10
  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
- )
22
11
  from trano.elements.system import Occupancy # type: ignore
23
12
  from trano.elements.types import Tilt # type: ignore
13
+ from vedo import Line # type: ignore
24
14
 
25
15
  from ifctrano.base import (
26
16
  GlobalId,
27
17
  settings,
28
18
  BaseModelConfig,
29
19
  CommonSurface,
30
- ROUNDING_FACTOR,
31
20
  CLASH_CLEARANCE,
32
21
  Vector,
22
+ BaseShow,
33
23
  )
34
24
  from ifctrano.bounding_box import OrientedBoundingBox
25
+ from ifctrano.construction import glass, Constructions
26
+ from ifctrano.utils import remove_non_alphanumeric, _round, get_building_elements
27
+
28
+ ROOF_VECTOR = Vector(x=0, y=0, z=1)
35
29
 
36
30
 
37
31
  def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
@@ -48,18 +42,14 @@ def initialize_tree(ifc_file: file) -> ifcopenshell.geom.tree:
48
42
  return tree
49
43
 
50
44
 
51
- def remove_non_alphanumeric(text: str) -> str:
52
- return re.sub(r"[^a-zA-Z0-9]", "", text).lower()
53
-
54
-
55
45
  class Space(GlobalId):
56
46
  name: Optional[str] = None
57
47
  bounding_box: OrientedBoundingBox
58
48
  entity: entity_instance
59
- average_room_height: float
60
- floor_area: float
61
- bounding_box_height: float
62
- bounding_box_volume: float
49
+ average_room_height: Annotated[float, BeforeValidator(_round)]
50
+ floor_area: Annotated[float, BeforeValidator(_round)]
51
+ bounding_box_height: Annotated[float, BeforeValidator(_round)]
52
+ bounding_box_volume: Annotated[float, BeforeValidator(_round)]
63
53
 
64
54
  @classmethod
65
55
  def from_entity(cls, entity: entity_instance) -> "Space":
@@ -84,8 +74,8 @@ class Space(GlobalId):
84
74
  )
85
75
 
86
76
  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
77
+ return round(self.bounding_box_volume) == round(
78
+ self.floor_area * self.average_room_height
89
79
  )
90
80
 
91
81
  def space_name(self) -> str:
@@ -93,59 +83,23 @@ class Space(GlobalId):
93
83
  return f"space_{main_name}{remove_non_alphanumeric(self.entity.GlobalId)}"
94
84
 
95
85
 
96
- material_1 = Material(
97
- name="material_1",
98
- thermal_conductivity=0.046,
99
- specific_heat_capacity=940,
100
- density=80,
101
- )
102
- construction = Construction(
103
- name="construction_4",
104
- layers=[
105
- Layer(material=material_1, thickness=0.18),
106
- ],
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
- )
136
-
137
-
138
86
  class SpaceBoundary(BaseModelConfig):
139
87
  bounding_box: OrientedBoundingBox
140
88
  entity: entity_instance
141
89
  common_surface: CommonSurface
142
90
  adjacent_spaces: List[Space] = Field(default_factory=list)
143
91
 
92
+ def __hash__(self) -> int:
93
+ return hash(self.common_surface)
94
+
144
95
  def boundary_name(self) -> str:
145
96
  return f"{self.entity.is_a()}_{remove_non_alphanumeric(self.entity.GlobalId)}"
146
97
 
147
- def model_element(
148
- self, exclude_entities: List[str], north_axis: Vector
98
+ def model_element( # noqa: PLR0911
99
+ self,
100
+ exclude_entities: List[str],
101
+ north_axis: Vector,
102
+ constructions: Constructions,
149
103
  ) -> Optional[BaseWall]:
150
104
  if self.entity.GlobalId in exclude_entities:
151
105
  return None
@@ -156,7 +110,7 @@ class SpaceBoundary(BaseModelConfig):
156
110
  surface=self.common_surface.area,
157
111
  azimuth=azimuth,
158
112
  tilt=Tilt.wall,
159
- construction=construction,
113
+ construction=constructions.get_construction(self.entity),
160
114
  )
161
115
  if "door" in self.entity.is_a().lower():
162
116
  return ExternalDoor(
@@ -164,7 +118,7 @@ class SpaceBoundary(BaseModelConfig):
164
118
  surface=self.common_surface.area,
165
119
  azimuth=azimuth,
166
120
  tilt=Tilt.wall,
167
- construction=construction,
121
+ construction=constructions.get_construction(self.entity),
168
122
  )
169
123
  if "window" in self.entity.is_a().lower():
170
124
  return Window(
@@ -180,7 +134,16 @@ class SpaceBoundary(BaseModelConfig):
180
134
  surface=self.common_surface.area,
181
135
  azimuth=azimuth,
182
136
  tilt=Tilt.ceiling,
183
- construction=construction,
137
+ construction=constructions.get_construction(self.entity),
138
+ )
139
+ if "slab" in self.entity.is_a().lower():
140
+ orientation = self.common_surface.orientation.dot(ROOF_VECTOR)
141
+ return ExternalWall(
142
+ name=self.boundary_name(),
143
+ surface=self.common_surface.area,
144
+ azimuth=azimuth,
145
+ tilt=Tilt.ceiling if orientation > 0 else Tilt.floor,
146
+ construction=constructions.get_construction(self.entity),
184
147
  )
185
148
 
186
149
  return None
@@ -206,20 +169,59 @@ class SpaceBoundary(BaseModelConfig):
206
169
  )
207
170
 
208
171
 
209
- class SpaceBoundaries(BaseModel):
172
+ def _reassign_constructions(external_boundaries: List[BaseWall]) -> None:
173
+ results = {
174
+ tuple(sorted([ex.name, ex_.name])): (ex, ex_)
175
+ for ex in external_boundaries
176
+ for ex_ in external_boundaries
177
+ if ex.construction.name != ex_.construction.name
178
+ and ex.azimuth == ex_.azimuth
179
+ and isinstance(ex, ExternalWall)
180
+ and isinstance(ex_, ExternalWall)
181
+ and ex.tilt == Tilt.wall
182
+ and ex_.tilt == Tilt.wall
183
+ }
184
+ if results:
185
+ for walls in results.values():
186
+ construction = next(w.construction for w in walls)
187
+ for w in walls:
188
+ w.construction = construction.model_copy(deep=True)
189
+
190
+
191
+ class SpaceBoundaries(BaseShow):
210
192
  space: Space
211
193
  boundaries: List[SpaceBoundary] = Field(default_factory=list)
212
194
 
195
+ def description(self) -> set[tuple[float, tuple[float, ...], Any, str]]:
196
+ return {b.description() for b in self.boundaries}
197
+
198
+ def lines(self) -> List[Line]:
199
+ lines = []
200
+ for boundary in self.boundaries:
201
+ lines += boundary.common_surface.lines()
202
+ return lines
203
+
204
+ def remove(self, space_boundaries: List[SpaceBoundary]) -> None:
205
+ for space_boundary in space_boundaries:
206
+ if space_boundary in self.boundaries:
207
+ self.boundaries.remove(space_boundary)
208
+
213
209
  def model(
214
- self, exclude_entities: List[str], north_axis: Vector
210
+ self,
211
+ exclude_entities: List[str],
212
+ north_axis: Vector,
213
+ constructions: Constructions,
215
214
  ) -> 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
215
+ external_boundaries = []
216
+ for boundary in self.boundaries:
217
+ boundary_model = boundary.model_element(
218
+ exclude_entities, north_axis, constructions
219
+ )
220
+ if boundary_model:
221
+ external_boundaries.append(boundary_model)
222
+
223
+ _reassign_constructions(external_boundaries)
224
+
223
225
  return TranoSpace(
224
226
  name=self.space.space_name(),
225
227
  occupancy=Occupancy(),
@@ -238,28 +240,28 @@ class SpaceBoundaries(BaseModel):
238
240
  space: entity_instance,
239
241
  ) -> "SpaceBoundaries":
240
242
  space_ = Space.from_entity(space)
243
+
244
+ elements = get_building_elements(ifcopenshell_file)
241
245
  clashes = tree.clash_clearance_many(
242
246
  [space],
243
- ifcopenshell_file.by_type("IfcWall")
244
- + ifcopenshell_file.by_type("IfcSlab")
245
- + ifcopenshell_file.by_type("IfcRoof")
246
- + ifcopenshell_file.by_type("IfcDoor")
247
- + ifcopenshell_file.by_type("IfcWindow"),
247
+ elements,
248
248
  clearance=CLASH_CLEARANCE,
249
249
  )
250
250
  space_boundaries = []
251
-
252
- for clash in clashes:
253
- elements = [
254
- ifcopenshell_file.by_guid(clash.a.get_argument(0)),
255
- ifcopenshell_file.by_guid(clash.b.get_argument(0)),
251
+ elements_ = {
252
+ entity
253
+ for c in clashes
254
+ for entity in [
255
+ ifcopenshell_file.by_guid(c.a.get_argument(0)),
256
+ ifcopenshell_file.by_guid(c.b.get_argument(0)),
256
257
  ]
257
- for element in elements:
258
- if element.GlobalId == space.GlobalId:
259
- continue
260
- space_boundary = SpaceBoundary.from_space_and_element(
261
- space_.bounding_box, element
262
- )
263
- if space_boundary:
264
- space_boundaries.append(space_boundary)
258
+ if entity.is_a() not in ["IfcSpace"]
259
+ }
260
+
261
+ for element in elements_:
262
+ space_boundary = SpaceBoundary.from_space_and_element(
263
+ space_.bounding_box, element
264
+ )
265
+ if space_boundary:
266
+ space_boundaries.append(space_boundary)
265
267
  return cls(space=space_, boundaries=space_boundaries)
ifctrano/types.py ADDED
@@ -0,0 +1,5 @@
1
+ from typing import Literal
2
+
3
+ BuildingElements = Literal[
4
+ "IfcWall", "IfcSlab", "IfcRoof", "IfcDoor", "IfcWindow", "IfcPlate"
5
+ ]
ifctrano/utils.py ADDED
@@ -0,0 +1,29 @@
1
+ import re
2
+ import uuid
3
+ from typing import get_args
4
+
5
+ from ifcopenshell import file, entity_instance
6
+
7
+ from ifctrano.base import ROUNDING_FACTOR
8
+ from ifctrano.types import BuildingElements
9
+
10
+
11
+ def remove_non_alphanumeric(text: str) -> str:
12
+ text = text.replace(" ", "_")
13
+ return re.sub(r"[^a-zA-Z0-9_]", "", text).lower()
14
+
15
+
16
+ def generate_alphanumeric_uuid() -> str:
17
+ return str(uuid.uuid4().hex).lower()
18
+
19
+
20
+ def _round(value: float) -> float:
21
+ return round(value, ROUNDING_FACTOR)
22
+
23
+
24
+ def get_building_elements(ifcopenshell_file: file) -> list[entity_instance]:
25
+ return [
26
+ e
27
+ for building_element in get_args(BuildingElements)
28
+ for e in ifcopenshell_file.by_type(building_element)
29
+ ]
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: ifctrano
3
- Version: 0.1.12
3
+ Version: 0.2.0
4
4
  Summary: Package for generating building energy simulation model from IFC
5
- Home-page: https://github.com/andoludo/ifctrano
6
5
  License: GPL V3
7
6
  Keywords: BIM,IFC,energy simulation,modelica,building energy simulation,buildings,ideas
8
7
  Author: Ando Andriamamonjy
@@ -12,11 +11,13 @@ Classifier: License :: Other/Proprietary License
12
11
  Classifier: Programming Language :: Python :: 3
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Dist: ifcopenshell (>=0.8.1.post1,<0.9.0)
16
- Requires-Dist: pydantic (>=2.10.6,<3.0.0)
16
+ Requires-Dist: open3d (>=0.19.0,<0.20.0)
17
17
  Requires-Dist: shapely (>=2.0.7,<3.0.0)
18
- Requires-Dist: trano (>=0.1.50,<0.2.0)
18
+ Requires-Dist: trano (>=0.2.0,<0.3.0)
19
19
  Requires-Dist: typer (>=0.12.5,<0.13.0)
20
+ Requires-Dist: vedo (>=2025.5.3,<2026.0.0)
20
21
  Project-URL: Repository, https://github.com/andoludo/ifctrano
21
22
  Description-Content-Type: text/markdown
22
23
 
@@ -0,0 +1,16 @@
1
+ ifctrano/__init__.py,sha256=UT1-izHuXPq06nSeKzgs6qoVRfc6RqIhabf-ZKgAXf4,76
2
+ ifctrano/base.py,sha256=upXv93ahpQHN-AKkl9MEsMvS3BGw7SFjG-oGJ2VNdWM,13812
3
+ ifctrano/bounding_box.py,sha256=ctHImomBY7C0sLWGxwXP0VzqdmtLzWeH0CNo6Y-4TAo,7334
4
+ ifctrano/building.py,sha256=E3D6Bg6MeHQ3JeBMMxMFwLRvmo38uPYyGON-G8uTE_k,8827
5
+ ifctrano/construction.py,sha256=KGiJJ6L0D7mK254GYl6CO7TKoTNoUSJeZCSHzbkhslo,7244
6
+ ifctrano/example/verification.ifc,sha256=tQ9QcubT_wrbb-sc1WRRwYpb2cbkWm3dnRfXdP5GTTg,131
7
+ ifctrano/exceptions.py,sha256=1FQeuuq_lVZ4CawwewQvkE8OlDQwhBTbKfmc2FiZodo,431
8
+ ifctrano/main.py,sha256=k6FyWD2hp_5tYkV-dqL42jdmfke53Jlkvz8-vc-VufA,4779
9
+ ifctrano/space_boundary.py,sha256=OOIZJFWU3fQ_nm4RcXjC4Y55mUxGe789eParP533LEY,9512
10
+ ifctrano/types.py,sha256=wxKVb2R4Dz58YzN4PzgXhuuVw-UYobSh9fnYWQrYlqQ,130
11
+ ifctrano/utils.py,sha256=AjQrIqbKAk4CWCcfeRHSZtWT6AAxQLyj-uDWPdUU6UU,712
12
+ ifctrano-0.2.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
+ ifctrano-0.2.0.dist-info/METADATA,sha256=j0yo3sbSbAAcxg-ceS8vDXrlPD-DPScHfGgDF1mU-lQ,3533
14
+ ifctrano-0.2.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
+ ifctrano-0.2.0.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
16
+ ifctrano-0.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.5.2
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,13 +0,0 @@
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=QH5KI8wFM4KsIRXNz_CVNtr3DWP3GdETtAAZGL84VEI,2703
8
- ifctrano/space_boundary.py,sha256=SxVXkBi8TNZfmPDFT-6epuewk8E9V1astVgcD3FT8ew,8617
9
- ifctrano-0.1.12.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
10
- ifctrano-0.1.12.dist-info/METADATA,sha256=aSroySKHppxHxEyq0v-RvwAEPhy70AqTL4gTRsP5oig,3490
11
- ifctrano-0.1.12.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
12
- ifctrano-0.1.12.dist-info/entry_points.txt,sha256=_2daDejazkphufyEu0m3lOeTio53WYmjol3KmSN0JM4,46
13
- ifctrano-0.1.12.dist-info/RECORD,,