ifctrano 0.1.12__py3-none-any.whl → 0.3.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/__init__.py +3 -0
- ifctrano/base.py +266 -13
- ifctrano/bounding_box.py +99 -169
- ifctrano/building.py +109 -54
- ifctrano/construction.py +227 -0
- ifctrano/example/verification.ifc +3 -3043
- ifctrano/exceptions.py +4 -0
- ifctrano/main.py +55 -2
- ifctrano/space_boundary.py +168 -98
- ifctrano/types.py +5 -0
- ifctrano/utils.py +29 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/METADATA +6 -5
- ifctrano-0.3.0.dist-info/RECORD +16 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/WHEEL +1 -1
- ifctrano-0.1.12.dist-info/RECORD +0 -13
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/LICENSE +0 -0
- {ifctrano-0.1.12.dist-info → ifctrano-0.3.0.dist-info}/entry_points.txt +0 -0
ifctrano/bounding_box.py
CHANGED
@@ -1,105 +1,47 @@
|
|
1
|
+
from itertools import combinations
|
1
2
|
from logging import getLogger
|
2
|
-
from typing import List, Optional, Any, Tuple
|
3
|
+
from typing import List, Optional, Any, Tuple
|
3
4
|
|
4
5
|
import ifcopenshell
|
5
|
-
import numpy as np
|
6
|
-
from ifcopenshell import entity_instance
|
7
6
|
import ifcopenshell.geom
|
7
|
+
import ifcopenshell.util.placement
|
8
8
|
import ifcopenshell.util.shape
|
9
|
+
import numpy as np
|
10
|
+
import open3d # type: ignore
|
11
|
+
from ifcopenshell import entity_instance
|
9
12
|
from pydantic import (
|
10
13
|
BaseModel,
|
11
14
|
Field,
|
15
|
+
ConfigDict,
|
12
16
|
)
|
13
|
-
from
|
17
|
+
from scipy.spatial import ConvexHull # type: ignore
|
18
|
+
from vedo import Line # type: ignore
|
14
19
|
|
15
20
|
from ifctrano.base import (
|
16
21
|
Point,
|
17
22
|
Vector,
|
18
|
-
P,
|
19
|
-
Sign,
|
20
|
-
CoordinateSystem,
|
21
23
|
Vertices,
|
22
24
|
BaseModelConfig,
|
23
25
|
settings,
|
24
26
|
CommonSurface,
|
25
27
|
AREA_TOLERANCE,
|
26
|
-
|
28
|
+
FaceVertices,
|
29
|
+
BaseShow,
|
27
30
|
)
|
28
|
-
from ifctrano.exceptions import
|
31
|
+
from ifctrano.exceptions import VectorWithNansError
|
29
32
|
|
30
33
|
logger = getLogger(__name__)
|
31
34
|
|
32
35
|
|
33
|
-
def get_normal(
|
34
|
-
centroid: Point,
|
35
|
-
difference: Point,
|
36
|
-
face_signs: List[Sign],
|
37
|
-
coordinate_system: CoordinateSystem,
|
38
|
-
) -> Vector:
|
39
|
-
point_0 = centroid + difference.s(face_signs[0])
|
40
|
-
point_1 = centroid + difference.s(face_signs[1])
|
41
|
-
point_2 = centroid + difference.s(face_signs[2])
|
42
|
-
vector_1 = coordinate_system.project((point_1 - point_0).to_array())
|
43
|
-
vector_2 = coordinate_system.project((point_2 - point_0).to_array())
|
44
|
-
array = (
|
45
|
-
(Vector.from_array(vector_1) * Vector.from_array(vector_2)).norm().to_array()
|
46
|
-
)
|
47
|
-
return Vector.from_array(array)
|
48
|
-
|
49
|
-
|
50
|
-
class Polygon2D(BaseModelConfig):
|
51
|
-
polygon: Polygon
|
52
|
-
normal: Vector
|
53
|
-
length: float
|
54
|
-
|
55
|
-
|
56
36
|
class BoundingBoxFace(BaseModelConfig):
|
57
|
-
vertices:
|
37
|
+
vertices: FaceVertices
|
58
38
|
normal: Vector
|
59
|
-
coordinate_system: CoordinateSystem
|
60
39
|
|
61
40
|
@classmethod
|
62
|
-
def build(
|
63
|
-
|
64
|
-
centroid: Point,
|
65
|
-
difference: Point,
|
66
|
-
face_signs: List[Sign],
|
67
|
-
coordinate_system: CoordinateSystem,
|
68
|
-
) -> "BoundingBoxFace":
|
69
|
-
if len(face_signs) != len(set(face_signs)):
|
70
|
-
raise BoundingBoxFaceError("Face signs must be unique")
|
71
|
-
normal = get_normal(centroid, difference, face_signs, coordinate_system)
|
72
|
-
vertices_ = [(centroid + difference.s(s)).to_list() for s in face_signs]
|
73
|
-
vertices_ = [*vertices_, vertices_[0]]
|
74
|
-
vertices__ = [coordinate_system.project(v) for v in vertices_]
|
75
|
-
vertices = Vertices.from_arrays(vertices__)
|
76
|
-
|
77
|
-
return cls(
|
78
|
-
vertices=vertices, normal=normal, coordinate_system=coordinate_system
|
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))
|
41
|
+
def build(cls, vertices: Vertices) -> "BoundingBoxFace":
|
42
|
+
face_vertices = vertices.to_face_vertices()
|
84
43
|
|
85
|
-
|
86
|
-
|
87
|
-
projected_vertices = coordinate_system.inverse(self.vertices.to_array())
|
88
|
-
projected_normal_index = Vector.from_array(
|
89
|
-
coordinate_system.inverse(self.normal.to_array())
|
90
|
-
).get_normal_index()
|
91
|
-
polygon = Polygon(
|
92
|
-
[
|
93
|
-
[v_ for i, v_ in enumerate(v) if i != projected_normal_index]
|
94
|
-
for v in projected_vertices.tolist()
|
95
|
-
]
|
96
|
-
)
|
97
|
-
|
98
|
-
return Polygon2D(
|
99
|
-
polygon=polygon,
|
100
|
-
normal=self.normal,
|
101
|
-
length=projected_vertices.tolist()[0][projected_normal_index],
|
102
|
-
)
|
44
|
+
return cls(vertices=face_vertices, normal=face_vertices.get_normal())
|
103
45
|
|
104
46
|
|
105
47
|
class BoundingBoxFaces(BaseModel):
|
@@ -110,56 +52,51 @@ class BoundingBoxFaces(BaseModel):
|
|
110
52
|
|
111
53
|
@classmethod
|
112
54
|
def build(
|
113
|
-
cls,
|
55
|
+
cls, box_points: np.ndarray[tuple[int, ...], np.dtype[np.float64]]
|
114
56
|
) -> "BoundingBoxFaces":
|
115
|
-
face_signs = [
|
116
|
-
[Sign(x=-1, y=-1, z=-1), Sign(y=-1, z=-1), Sign(z=-1), Sign(x=-1, z=-1)],
|
117
|
-
[Sign(x=-1, y=-1), Sign(y=-1), Sign(), Sign(x=-1)],
|
118
|
-
[
|
119
|
-
Sign(x=-1, y=-1, z=-1),
|
120
|
-
Sign(x=-1, y=1, z=-1),
|
121
|
-
Sign(x=-1, y=1, z=1),
|
122
|
-
Sign(x=-1, y=-1, z=1),
|
123
|
-
],
|
124
|
-
[
|
125
|
-
Sign(x=1, y=-1, z=-1),
|
126
|
-
Sign(x=1, y=1, z=-1),
|
127
|
-
Sign(x=1, y=1, z=1),
|
128
|
-
Sign(x=1, y=-1, z=1),
|
129
|
-
],
|
130
|
-
[
|
131
|
-
Sign(x=-1, y=-1, z=-1),
|
132
|
-
Sign(x=1, y=-1, z=-1),
|
133
|
-
Sign(x=1, y=-1, z=1),
|
134
|
-
Sign(x=-1, y=-1, z=1),
|
135
|
-
],
|
136
|
-
[
|
137
|
-
Sign(x=-1, y=1, z=-1),
|
138
|
-
Sign(x=1, y=1, z=-1),
|
139
|
-
Sign(x=1, y=1, z=1),
|
140
|
-
Sign(x=-1, y=1, z=1),
|
141
|
-
],
|
142
|
-
]
|
143
57
|
faces = [
|
144
|
-
|
145
|
-
|
58
|
+
[0, 1, 6, 3],
|
59
|
+
[2, 5, 4, 7],
|
60
|
+
[0, 3, 5, 2],
|
61
|
+
[1, 7, 4, 6],
|
62
|
+
[0, 2, 7, 1],
|
63
|
+
[3, 6, 4, 5],
|
64
|
+
]
|
65
|
+
faces_ = [
|
66
|
+
BoundingBoxFace.build(Vertices.from_arrays(np.array(box_points)[face]))
|
67
|
+
for face in faces
|
146
68
|
]
|
147
|
-
return cls(faces=
|
69
|
+
return cls(faces=faces_)
|
148
70
|
|
149
71
|
|
150
72
|
class ExtendCommonSurface(CommonSurface):
|
151
73
|
distance: float
|
152
74
|
|
153
75
|
def to_common_surface(self) -> CommonSurface:
|
154
|
-
return CommonSurface(
|
76
|
+
return CommonSurface(
|
77
|
+
area=self.area,
|
78
|
+
orientation=self.orientation,
|
79
|
+
main_vertices=self.main_vertices,
|
80
|
+
common_vertices=self.common_vertices,
|
81
|
+
)
|
155
82
|
|
156
83
|
|
157
|
-
class OrientedBoundingBox(
|
84
|
+
class OrientedBoundingBox(BaseShow):
|
85
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
158
86
|
faces: BoundingBoxFaces
|
159
87
|
centroid: Point
|
160
88
|
area_tolerance: float = Field(default=AREA_TOLERANCE)
|
161
89
|
volume: float
|
162
90
|
height: float
|
91
|
+
entity: Optional[entity_instance] = None
|
92
|
+
|
93
|
+
def lines(self) -> List[Line]:
|
94
|
+
lines = []
|
95
|
+
for f in self.faces.faces:
|
96
|
+
face = f.vertices.to_list()
|
97
|
+
for a, b in combinations(face, 2):
|
98
|
+
lines.append(Line(a, b))
|
99
|
+
return lines
|
163
100
|
|
164
101
|
def intersect_faces(self, other: "OrientedBoundingBox") -> Optional[CommonSurface]:
|
165
102
|
extend_surfaces = []
|
@@ -167,27 +104,42 @@ class OrientedBoundingBox(BaseModel):
|
|
167
104
|
|
168
105
|
for other_face in other.faces.faces:
|
169
106
|
vector = face.normal * other_face.normal
|
170
|
-
if vector.
|
171
|
-
|
172
|
-
|
173
|
-
|
107
|
+
if vector.is_null():
|
108
|
+
projected_face_1 = face.vertices.project(face.vertices)
|
109
|
+
projected_face_2 = face.vertices.project(other_face.vertices)
|
110
|
+
polygon_1 = projected_face_1.to_polygon()
|
111
|
+
polygon_2 = projected_face_2.to_polygon()
|
112
|
+
intersection = polygon_2.intersection(polygon_1)
|
174
113
|
if intersection.area > self.area_tolerance:
|
175
|
-
distance =
|
114
|
+
distance = projected_face_1.get_distance(projected_face_2)
|
176
115
|
area = intersection.area
|
177
|
-
direction_vector = (other.centroid - self.centroid).norm()
|
178
116
|
try:
|
179
|
-
|
117
|
+
direction_vector = (
|
118
|
+
other.centroid - self.centroid
|
119
|
+
).normalize()
|
120
|
+
orientation = direction_vector.project(
|
121
|
+
face.normal
|
122
|
+
).normalize()
|
180
123
|
except VectorWithNansError as e:
|
181
|
-
logger.
|
182
|
-
"Orientation vector was not properly computed when computing the intersection between"
|
183
|
-
f"two elements
|
124
|
+
logger.warning(
|
125
|
+
"Orientation vector was not properly computed when computing the intersection between "
|
126
|
+
f"two elements "
|
127
|
+
f"({(self.entity.GlobalId, self.entity.is_a(), self.entity.Name) if self.entity else None}" # noqa: E501
|
128
|
+
f", {(other.entity.GlobalId, other.entity.is_a(), other.entity.Name)if other.entity else None}). Error: {e}" # noqa: E501
|
184
129
|
)
|
185
130
|
continue
|
186
131
|
extend_surfaces.append(
|
187
132
|
ExtendCommonSurface(
|
188
|
-
distance=distance,
|
133
|
+
distance=distance,
|
134
|
+
area=area,
|
135
|
+
orientation=orientation,
|
136
|
+
main_vertices=face.vertices,
|
137
|
+
common_vertices=projected_face_1.common_vertices(
|
138
|
+
intersection
|
139
|
+
),
|
189
140
|
)
|
190
141
|
)
|
142
|
+
|
191
143
|
if extend_surfaces:
|
192
144
|
if not all(
|
193
145
|
e.orientation == extend_surfaces[0].orientation for e in extend_surfaces
|
@@ -199,69 +151,47 @@ class OrientedBoundingBox(BaseModel):
|
|
199
151
|
extend_surfaces, key=lambda x: x.distance, reverse=True
|
200
152
|
)[-1]
|
201
153
|
return extend_surface.to_common_surface()
|
154
|
+
else:
|
155
|
+
logger.warning(
|
156
|
+
"No common surfaces found between between "
|
157
|
+
f"two elements "
|
158
|
+
f"({(self.entity.GlobalId, self.entity.is_a(), self.entity.Name) if self.entity else None}, "
|
159
|
+
f"{(other.entity.GlobalId, other.entity.is_a(), other.entity.Name) if other.entity else None})."
|
160
|
+
)
|
202
161
|
return None
|
203
162
|
|
204
163
|
@classmethod
|
205
164
|
def from_vertices(
|
206
|
-
cls,
|
165
|
+
cls,
|
166
|
+
vertices: np.ndarray[tuple[int, ...], np.dtype[np.float64]],
|
167
|
+
entity: Optional[entity_instance] = None,
|
207
168
|
) -> "OrientedBoundingBox":
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
v, vect = np.linalg.eig(np.round(cov, ROUNDING_FACTOR))
|
212
|
-
tvect = np.transpose(vect)
|
213
|
-
points_r = np.dot(points, np.linalg.inv(tvect))
|
214
|
-
|
215
|
-
co_min = np.min(points_r, axis=0)
|
216
|
-
co_max = np.max(points_r, axis=0)
|
217
|
-
|
218
|
-
xmin, xmax = co_min[0], co_max[0]
|
219
|
-
ymin, ymax = co_min[1], co_max[1]
|
220
|
-
zmin, zmax = co_min[2], co_max[2]
|
221
|
-
|
222
|
-
x_len = xmax - xmin
|
223
|
-
y_len = ymax - ymin
|
224
|
-
z_len = zmax - zmin
|
225
|
-
xdif = x_len * 0.5
|
226
|
-
ydif = y_len * 0.5
|
227
|
-
zdif = z_len * 0.5
|
228
|
-
|
229
|
-
cx = xmin + xdif
|
230
|
-
cy = ymin + ydif
|
231
|
-
cz = zmin + zdif
|
232
|
-
corners = np.array(
|
233
|
-
[
|
234
|
-
[cx - xdif, cy - ydif, cz - zdif],
|
235
|
-
[cx - xdif, cy + ydif, cz - zdif],
|
236
|
-
[cx - xdif, cy + ydif, cz + zdif],
|
237
|
-
[cx - xdif, cy - ydif, cz + zdif],
|
238
|
-
[cx + xdif, cy + ydif, cz + zdif],
|
239
|
-
[cx + xdif, cy + ydif, cz - zdif],
|
240
|
-
[cx + xdif, cy - ydif, cz + zdif],
|
241
|
-
[cx + xdif, cy - ydif, cz - zdif],
|
242
|
-
]
|
169
|
+
points_ = open3d.utility.Vector3dVector(vertices)
|
170
|
+
mobb = open3d.geometry.OrientedBoundingBox.create_from_points_minimal(
|
171
|
+
points_, robust=True
|
243
172
|
)
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
coordinate_system = CoordinateSystem.from_array(tvect)
|
250
|
-
c = P(x=cx, y=cy, z=cz)
|
251
|
-
d = P(x=xdif, y=ydif, z=zdif)
|
252
|
-
faces = BoundingBoxFaces.build(c, d, coordinate_system)
|
173
|
+
height = (mobb.get_max_bound() - mobb.get_min_bound())[
|
174
|
+
2
|
175
|
+
] # assuming that height is the z axis
|
176
|
+
centroid = Point.from_array(mobb.get_center())
|
177
|
+
faces = BoundingBoxFaces.build(np.array(mobb.get_box_points()))
|
253
178
|
return cls(
|
254
179
|
faces=faces,
|
255
|
-
centroid=
|
256
|
-
volume=
|
257
|
-
height=
|
180
|
+
centroid=centroid,
|
181
|
+
volume=mobb.volume(),
|
182
|
+
height=height,
|
183
|
+
entity=entity,
|
258
184
|
)
|
259
185
|
|
260
186
|
@classmethod
|
261
187
|
def from_entity(cls, entity: entity_instance) -> "OrientedBoundingBox":
|
262
188
|
entity_shape = ifcopenshell.geom.create_shape(settings, entity)
|
263
|
-
|
264
189
|
vertices = ifcopenshell.util.shape.get_shape_vertices(
|
265
190
|
entity_shape, entity_shape.geometry # type: ignore
|
266
191
|
)
|
267
|
-
|
192
|
+
vertices_ = Vertices.from_arrays(np.asarray(vertices))
|
193
|
+
hull = ConvexHull(vertices_.to_array())
|
194
|
+
vertices_ = Vertices.from_arrays(vertices_.to_array()[hull.vertices])
|
195
|
+
points_ = open3d.utility.Vector3dVector(vertices_.to_array())
|
196
|
+
aab = open3d.geometry.AxisAlignedBoundingBox.create_from_points(points_)
|
197
|
+
return cls.from_vertices(aab.get_box_points(), entity)
|
ifctrano/building.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import List, Tuple, Any, Optional
|
3
|
+
from typing import List, Tuple, Any, Optional, Set
|
4
4
|
|
5
5
|
import ifcopenshell
|
6
6
|
from ifcopenshell import file, entity_instance
|
@@ -9,15 +9,16 @@ from trano.elements import InternalElement # type: ignore
|
|
9
9
|
from trano.elements.library.library import Library # type: ignore
|
10
10
|
from trano.elements.types import Tilt # type: ignore
|
11
11
|
from trano.topology import Network # type: ignore
|
12
|
+
from vedo import Line # type: ignore
|
12
13
|
|
13
|
-
from ifctrano.base import BaseModelConfig, Libraries, Vector
|
14
|
+
from ifctrano.base import BaseModelConfig, Libraries, Vector, BaseShow, CommonSurface
|
14
15
|
from ifctrano.exceptions import IfcFileNotFoundError, NoIfcSpaceFoundError
|
15
16
|
from ifctrano.space_boundary import (
|
16
17
|
SpaceBoundaries,
|
17
18
|
initialize_tree,
|
18
19
|
Space,
|
19
|
-
construction,
|
20
20
|
)
|
21
|
+
from ifctrano.construction import Constructions, default_construction
|
21
22
|
|
22
23
|
|
23
24
|
def get_spaces(ifcopenshell_file: file) -> List[entity_instance]:
|
@@ -28,6 +29,7 @@ class IfcInternalElement(BaseModelConfig):
|
|
28
29
|
spaces: List[Space]
|
29
30
|
element: entity_instance
|
30
31
|
area: float
|
32
|
+
common_surface: CommonSurface
|
31
33
|
|
32
34
|
def __hash__(self) -> int:
|
33
35
|
return hash(
|
@@ -49,23 +51,108 @@ class IfcInternalElement(BaseModelConfig):
|
|
49
51
|
self.area,
|
50
52
|
)
|
51
53
|
|
54
|
+
def lines(self) -> List[Line]:
|
55
|
+
lines = []
|
56
|
+
if self.common_surface:
|
57
|
+
lines += self.common_surface.lines()
|
58
|
+
return lines
|
52
59
|
|
53
|
-
|
60
|
+
|
61
|
+
class InternalElements(BaseShow):
|
54
62
|
elements: List[IfcInternalElement] = Field(default_factory=list)
|
55
63
|
|
56
64
|
def internal_element_ids(self) -> List[str]:
|
57
65
|
return list({e.element.GlobalId for e in self.elements})
|
58
66
|
|
59
|
-
def description(self) ->
|
60
|
-
return
|
67
|
+
def description(self) -> Set[Tuple[Any, Any, str, float]]:
|
68
|
+
return set( # noqa: C414
|
69
|
+
sorted([element.description() for element in self.elements])
|
70
|
+
)
|
71
|
+
|
72
|
+
|
73
|
+
def get_internal_elements(space1_boundaries: List[SpaceBoundaries]) -> InternalElements:
|
74
|
+
elements = []
|
75
|
+
seen = set()
|
76
|
+
common_boundaries = []
|
77
|
+
for space_boundaries_ in space1_boundaries:
|
78
|
+
for space_boundaries__ in space1_boundaries:
|
79
|
+
space_1 = space_boundaries_.space
|
80
|
+
space_2 = space_boundaries__.space
|
81
|
+
|
82
|
+
if (
|
83
|
+
space_1.global_id == space_2.global_id
|
84
|
+
and (space_1.global_id, space_2.global_id) in seen
|
85
|
+
):
|
86
|
+
continue
|
87
|
+
seen.update(
|
88
|
+
{
|
89
|
+
(space_1.global_id, space_2.global_id),
|
90
|
+
(space_2.global_id, space_1.global_id),
|
91
|
+
}
|
92
|
+
)
|
93
|
+
common_surface = space_1.bounding_box.intersect_faces(space_2.bounding_box)
|
94
|
+
|
95
|
+
for boundary in space_boundaries_.boundaries:
|
96
|
+
for boundary_ in space_boundaries__.boundaries:
|
97
|
+
if (
|
98
|
+
boundary.entity.GlobalId == boundary_.entity.GlobalId
|
99
|
+
and boundary.common_surface
|
100
|
+
and boundary_.common_surface
|
101
|
+
and common_surface
|
102
|
+
and (
|
103
|
+
boundary.common_surface.orientation
|
104
|
+
* common_surface.orientation
|
105
|
+
).is_null()
|
106
|
+
and (
|
107
|
+
boundary_.common_surface.orientation
|
108
|
+
* common_surface.orientation
|
109
|
+
).is_null()
|
110
|
+
) and boundary.common_surface.orientation.dot(
|
111
|
+
boundary_.common_surface.orientation
|
112
|
+
) < 0:
|
113
|
+
common_boundaries.extend([boundary, boundary_])
|
114
|
+
common_surface = sorted(
|
115
|
+
[boundary.common_surface, boundary_.common_surface],
|
116
|
+
key=lambda s: s.area,
|
117
|
+
)[0]
|
118
|
+
common_surface.exterior = False
|
119
|
+
elements.append(
|
120
|
+
IfcInternalElement(
|
121
|
+
spaces=[space_1, space_2],
|
122
|
+
element=boundary_.entity,
|
123
|
+
area=common_surface.area,
|
124
|
+
common_surface=common_surface,
|
125
|
+
)
|
126
|
+
)
|
127
|
+
for space_boundaries_ in space1_boundaries:
|
128
|
+
space_boundaries_.remove(common_boundaries)
|
129
|
+
return InternalElements(elements=list(set(elements)))
|
61
130
|
|
62
131
|
|
63
|
-
class Building(
|
132
|
+
class Building(BaseShow):
|
64
133
|
name: str
|
65
134
|
space_boundaries: List[SpaceBoundaries]
|
66
135
|
ifc_file: file
|
67
136
|
parent_folder: Path
|
68
137
|
internal_elements: InternalElements = Field(default_factory=InternalElements)
|
138
|
+
constructions: Constructions
|
139
|
+
|
140
|
+
def get_boundaries(self, space_id: str) -> SpaceBoundaries:
|
141
|
+
return next(
|
142
|
+
sb for sb in self.space_boundaries if sb.space.global_id == space_id
|
143
|
+
)
|
144
|
+
|
145
|
+
def description(self) -> list[list[tuple[float, tuple[float, ...], Any, str]]]:
|
146
|
+
return sorted([sorted(b.description()) for b in self.space_boundaries])
|
147
|
+
|
148
|
+
def lines(self) -> List[Line]:
|
149
|
+
lines = []
|
150
|
+
for space_boundaries_ in [
|
151
|
+
*self.space_boundaries,
|
152
|
+
*self.internal_elements.elements,
|
153
|
+
]:
|
154
|
+
lines += space_boundaries_.lines() # type: ignore
|
155
|
+
return lines
|
69
156
|
|
70
157
|
@field_validator("name")
|
71
158
|
@classmethod
|
@@ -86,6 +173,7 @@ class Building(BaseModelConfig):
|
|
86
173
|
ifc_file = ifcopenshell.open(str(ifc_file_path))
|
87
174
|
tree = initialize_tree(ifc_file)
|
88
175
|
spaces = get_spaces(ifc_file)
|
176
|
+
constructions = Constructions.from_ifc(ifc_file)
|
89
177
|
if selected_spaces_global_id:
|
90
178
|
spaces = [
|
91
179
|
space for space in spaces if space.GlobalId in selected_spaces_global_id
|
@@ -100,6 +188,7 @@ class Building(BaseModelConfig):
|
|
100
188
|
ifc_file=ifc_file,
|
101
189
|
parent_folder=ifc_file_path.parent,
|
102
190
|
name=ifc_file_path.stem,
|
191
|
+
constructions=constructions,
|
103
192
|
)
|
104
193
|
|
105
194
|
@model_validator(mode="after")
|
@@ -108,58 +197,21 @@ class Building(BaseModelConfig):
|
|
108
197
|
return self
|
109
198
|
|
110
199
|
def get_adjacency(self) -> InternalElements:
|
111
|
-
|
112
|
-
for space_boundaries_ in self.space_boundaries:
|
113
|
-
for space_boundaries__ in self.space_boundaries:
|
114
|
-
space_1 = space_boundaries_.space
|
115
|
-
space_2 = space_boundaries__.space
|
116
|
-
if space_1.global_id == space_2.global_id:
|
117
|
-
continue
|
118
|
-
common_surface = space_1.bounding_box.intersect_faces(
|
119
|
-
space_2.bounding_box
|
120
|
-
)
|
121
|
-
for boundary in space_boundaries_.boundaries:
|
122
|
-
for boundary_ in space_boundaries__.boundaries:
|
123
|
-
if (
|
124
|
-
boundary.entity.GlobalId == boundary_.entity.GlobalId
|
125
|
-
and boundary.common_surface
|
126
|
-
and boundary_.common_surface
|
127
|
-
and common_surface
|
128
|
-
and (
|
129
|
-
boundary.common_surface.orientation
|
130
|
-
* common_surface.orientation
|
131
|
-
).is_a_zero()
|
132
|
-
and (
|
133
|
-
boundary_.common_surface.orientation
|
134
|
-
* common_surface.orientation
|
135
|
-
).is_a_zero()
|
136
|
-
) and boundary.common_surface.orientation.dot(
|
137
|
-
boundary_.common_surface.orientation
|
138
|
-
) < 0:
|
139
|
-
elements.append( # noqa: PERF401
|
140
|
-
IfcInternalElement(
|
141
|
-
spaces=[space_1, space_2],
|
142
|
-
element=boundary_.entity,
|
143
|
-
area=min(
|
144
|
-
common_surface.area,
|
145
|
-
boundary.common_surface.area,
|
146
|
-
boundary_.common_surface.area,
|
147
|
-
),
|
148
|
-
)
|
149
|
-
)
|
150
|
-
return InternalElements(elements=list(set(elements)))
|
200
|
+
return get_internal_elements(self.space_boundaries)
|
151
201
|
|
152
202
|
@validate_call
|
153
|
-
def
|
203
|
+
def create_network(
|
154
204
|
self,
|
155
205
|
library: Libraries = "Buildings",
|
156
206
|
north_axis: Optional[Vector] = None,
|
157
|
-
) ->
|
207
|
+
) -> Network:
|
158
208
|
north_axis = north_axis or Vector(x=0, y=1, z=0)
|
159
209
|
network = Network(name=self.name, library=Library.from_configuration(library))
|
160
210
|
spaces = {
|
161
211
|
space_boundary.space.global_id: space_boundary.model(
|
162
|
-
self.internal_elements.internal_element_ids(),
|
212
|
+
self.internal_elements.internal_element_ids(),
|
213
|
+
north_axis,
|
214
|
+
self.constructions,
|
163
215
|
)
|
164
216
|
for space_boundary in self.space_boundaries
|
165
217
|
}
|
@@ -173,13 +225,16 @@ class Building(BaseModelConfig):
|
|
173
225
|
spaces[space_2.global_id],
|
174
226
|
InternalElement(
|
175
227
|
azimuth=10,
|
176
|
-
construction=
|
228
|
+
construction=default_construction,
|
177
229
|
surface=internal_element.area,
|
178
230
|
tilt=Tilt.wall,
|
179
231
|
),
|
180
232
|
)
|
181
|
-
return network
|
233
|
+
return network
|
234
|
+
|
235
|
+
def get_model(self) -> str:
|
236
|
+
return str(self.create_network().model())
|
182
237
|
|
183
238
|
def save_model(self, library: Libraries = "Buildings") -> None:
|
184
|
-
model_ = self.
|
185
|
-
Path(self.parent_folder.joinpath(f"{self.name}.mo")).write_text(model_)
|
239
|
+
model_ = self.create_network(library)
|
240
|
+
Path(self.parent_folder.joinpath(f"{self.name}.mo")).write_text(model_.model())
|