Skip to content

Commit a456679

Browse files
sanzoghenzoMoult
authored andcommitted
fix: ifcclash bcfxml export
1 parent 4ee3458 commit a456679

File tree

13 files changed

+316
-151
lines changed

13 files changed

+316
-151
lines changed

src/bcf/requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ mypy
33
pylint
44
isort
55
xsdata
6-
tox
6+
tox==3.27.1
77
tox-conda

src/bcf/src/bcf/geometry.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from typing import Optional
2+
13
import numpy as np
24
from numpy.typing import NDArray
35

46

5-
def calc_camera_vectors(
7+
def camera_vectors_from_element_placement(
68
elem_placement: NDArray[np.float_],
79
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
810
"""
@@ -15,19 +17,37 @@ def calc_camera_vectors(
1517
Camera position, direction and up vectors
1618
"""
1719
target_position = elem_placement[:3, 3]
18-
camera_position = target_position + np.array((5, 5, 5))
19-
camera_direction = unit_vector(camera_position - target_position)
20+
return camera_vectors_from_target_position(target_position)
21+
22+
23+
def camera_vectors_from_target_position(
24+
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
25+
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
26+
"""
27+
Calculate the vectors of a camera pointing to a target point.
28+
29+
Args:
30+
target_position: point the camera is pointing to.
31+
camera_offset: offset of the camera from the target point.
32+
33+
Returns:
34+
Camera position, direction and up vectors
35+
"""
36+
camera_offset = np.array((5, 5, 5)) if offset is None else offset
37+
camera_position = target_position + camera_offset
38+
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
2039
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
2140
camera_up = unit_vector(np.cross(camera_direction, camera_right))
22-
rotation_transform = np.eye(4)
23-
rotation_transform[0, :3] = camera_right
24-
rotation_transform[1, :3] = camera_up
25-
rotation_transform[2, :3] = camera_direction
26-
translation_transform = np.eye(4)
27-
translation_transform[:3, -1] = -camera_position
28-
look_at_transform = np.matmul(rotation_transform, translation_transform)
29-
mat = np.linalg.inv(look_at_transform)
30-
return camera_position, -mat[:3, 2], mat[:3, 1]
41+
return camera_position, camera_direction, camera_up
42+
# rotation_transform = np.eye(4)
43+
# rotation_transform[0, :3] = camera_right
44+
# rotation_transform[1, :3] = camera_up
45+
# rotation_transform[2, :3] = camera_direction
46+
# translation_transform = np.eye(4)
47+
# translation_transform[:3, -1] = -camera_position
48+
# look_at_transform = np.matmul(rotation_transform, translation_transform)
49+
# mat = np.linalg.inv(look_at_transform)
50+
# return camera_position, -mat[:3, 2], mat[:3, 1]
3151

3252

3353
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:

src/bcf/src/bcf/v2/bcfxml.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import warnings
44
import zipfile
55
from pathlib import Path
6-
from typing import Any, Optional, TypeVar
6+
from typing import Any, NoReturn, Optional, TypeVar
77

88
import bcf.v2.model as mdl
99
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
@@ -50,7 +50,7 @@ def version(self) -> mdl.Version:
5050
self._version = (
5151
self._xml_handler.parse(self._zip_file.read("bcf.version"), mdl.Version)
5252
if self._zip_file
53-
else mdl.Version(version_id="3.0")
53+
else mdl.Version(version_id="2.1")
5454
)
5555
return self._version
5656

@@ -210,10 +210,12 @@ def add_topic(
210210
self.topics[topic_handler.guid] = topic_handler
211211
return topic_handler
212212

213-
def __eq__(self, other: object) -> bool:
214-
if not isinstance(other, BcfXml):
215-
raise TypeError("Equality needs a BcfXml object.")
216-
return self.version == other.version and self.project_info == other.project_info
213+
def __eq__(self, other: object) -> bool | NoReturn:
214+
return (
215+
self.version == other.version and self.project_info == other.project_info
216+
if isinstance(other, BcfXml)
217+
else NotImplemented
218+
)
217219

218220
# region Deprecated methods
219221
def new_project(self) -> "BcfXml":

src/bcf/src/bcf/v2/topic.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import uuid
44
import zipfile
55
from pathlib import Path
6-
from typing import Any, Optional
6+
from typing import Any, NoReturn, Optional
77

8+
import numpy as np
89
from ifcopenshell import entity_instance
10+
from numpy.typing import NDArray
911
from xsdata.models.datatype import XmlDateTime
1012

1113
import bcf.v2.model as mdl
@@ -226,15 +228,29 @@ def add_viewpoint(self, element: entity_instance) -> None:
226228
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
227229
self.add_visinfo_handler(new_viewpoint)
228230

231+
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
232+
"""
233+
Add a viewpoint tergeting an IFC element to the topic.
234+
235+
Args:
236+
element: The IFC element.
237+
"""
238+
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
239+
position, *guids, xml_handler=self._xml_handler
240+
)
241+
self.add_visinfo_handler(vi_handler)
242+
229243
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
230244
self.viewpoints[new_viewpoint.guid] = new_viewpoint
231245
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
232246

233-
def __eq__(self, other: object) -> bool:
234-
if not isinstance(other, TopicHandler):
235-
raise TypeError("Equality needs a BcfXml object.")
247+
def __eq__(self, other: object) -> bool | NoReturn:
236248
return (
237-
self.markup == other.markup
238-
and self.viewpoints == other.viewpoints
239-
and self.bim_snippet == other.bim_snippet
249+
(
250+
self.markup == other.markup
251+
and self.viewpoints == other.viewpoints
252+
and self.bim_snippet == other.bim_snippet
253+
)
254+
if isinstance(other, TopicHandler)
255+
else NotImplemented
240256
)

src/bcf/src/bcf/v2/visinfo.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
from numpy.typing import NDArray
1010

1111
import bcf.v2.model as mdl
12-
from bcf.geometry import calc_camera_vectors
12+
from bcf.geometry import (
13+
camera_vectors_from_element_placement,
14+
camera_vectors_from_target_position,
15+
)
1316
from bcf.inmemory_zipfile import ZipFileInterface
1417
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
1518

@@ -169,6 +172,29 @@ def create_new(
169172
xml_handler = xml_handler or XmlParserSerializer()
170173
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
171174

175+
@classmethod
176+
def create_from_point_and_guids(
177+
cls,
178+
position: NDArray[np.float_],
179+
*guids: str,
180+
xml_handler: Optional[AbstractXmlParserSerializer] = None,
181+
) -> "VisualizationInfoHandler":
182+
"""
183+
Create a new VisualizationInfoHandler object from an IFC element.
184+
185+
Args:
186+
position: target point coordinates.
187+
*guids: One or more IFC element GUID.
188+
xml_handler: The XML handler to use.
189+
190+
Returns:
191+
The VisualizationInfoHandler object.
192+
"""
193+
xml_handler = xml_handler or XmlParserSerializer()
194+
return cls(
195+
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
196+
)
197+
172198

173199
@lru_cache(maxsize=None)
174200
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
@@ -192,18 +218,39 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
192218
)
193219

194220

195-
def build_components(guid: str) -> mdl.Components:
221+
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
222+
"""
223+
Return a BCF viewpoint of an IFC element.
224+
225+
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
226+
227+
Args:
228+
position: target point coordinates.
229+
*guids: One or more IFC element GUID.
230+
231+
Returns:
232+
The BCF viewpoint definition.
233+
"""
234+
return mdl.VisualizationInfo(
235+
guid=str(uuid.uuid4()),
236+
components=build_components(*guids),
237+
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
238+
)
239+
240+
241+
def build_components(*guids: str) -> mdl.Components:
196242
"""
197243
Return the BCF components from an IFC element GUID.
198244
199245
Args:
200-
guid: The IFC element GUID.
246+
*guids: One or more IFC element GUID.
201247
202248
Returns:
203249
The BCF components definition.
204250
"""
251+
components = [mdl.Component(ifc_guid=guid) for guid in guids]
205252
return mdl.Components(
206-
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
253+
selection=mdl.ComponentSelection(component=components),
207254
visibility=mdl.ComponentVisibility(default_visibility=True),
208255
)
209256

@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
218265
Returns:
219266
The BCF camera definition.
220267
"""
221-
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
268+
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
222269

223270

224271
def build_camera_from_vectors(

src/bcf/src/bcf/v3/bcfxml.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import warnings
44
import zipfile
55
from pathlib import Path
6-
from typing import Any, Optional, TypeVar
6+
from typing import Any, NoReturn, Optional, TypeVar
77

88
import bcf.v3.model as mdl
99
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
@@ -224,13 +224,13 @@ def add_topic(
224224
self.topics[topic_handler.guid] = topic_handler
225225
return topic_handler
226226

227-
def __eq__(self, other: object) -> bool:
228-
if not isinstance(other, BcfXml):
229-
raise TypeError("Equality needs a BcfXml object.")
227+
def __eq__(self, other: object) -> bool | NoReturn:
230228
return (
231229
self.version == other.version
232230
and self.project_info == other.project_info
233231
and self.extensions == other.extensions
232+
if isinstance(other, BcfXml)
233+
else NotImplemented
234234
)
235235

236236
# region Deprecated methods

src/bcf/src/bcf/v3/topic.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import uuid
44
import zipfile
55
from pathlib import Path
6-
from typing import Any, Optional
6+
from typing import Any, NoReturn, Optional
77

8+
import numpy as np
89
from ifcopenshell import entity_instance
10+
from numpy.typing import NDArray
911
from xsdata.models.datatype import XmlDateTime
1012

1113
import bcf.v3.model as mdl
@@ -73,7 +75,7 @@ def bim_snippet(self, value: bytes) -> None:
7375
self._bim_snippet = value
7476

7577
@property
76-
def viewpoints(self) -> Optional[VisualizationInfoHandler]:
78+
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
7779
if (
7880
not self._viewpoints
7981
and self._topic_dir
@@ -143,7 +145,7 @@ def save(self, destination_zip: ZipFileInterface) -> None:
143145
self._save_viewpoints(destination_zip, topic_dir)
144146
self._save_bim_snippet(destination_zip)
145147

146-
def _save_viewpoints(self, destination_zip, topic_dir) -> None:
148+
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
147149
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
148150
return
149151
for vpt in viewpoints:
@@ -172,17 +174,31 @@ def add_viewpoint(self, element: entity_instance) -> None:
172174
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
173175
self.add_visinfo_handler(new_viewpoint)
174176

177+
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
178+
"""
179+
Add a viewpoint tergeting an IFC element to the topic.
180+
181+
Args:
182+
element: The IFC element.
183+
"""
184+
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
185+
position, *guids, xml_handler=self._xml_handler
186+
)
187+
self.add_visinfo_handler(vi_handler)
188+
175189
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
176190
self.viewpoints[new_viewpoint.guid] = new_viewpoint
177191
if self.topic.viewpoints is None:
178192
self.topic.viewpoints = mdl.TopicViewpoints()
179193
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
180194

181-
def __eq__(self, other: object) -> bool:
182-
if not isinstance(other, TopicHandler):
183-
raise TypeError("Equality needs a BcfXml object.")
195+
def __eq__(self, other: object) -> bool | NoReturn:
184196
return (
185-
self.markup == other.markup
186-
and self.viewpoints == other.viewpoints
187-
and self.bim_snippet == other.bim_snippet
197+
(
198+
self.markup == other.markup
199+
and self.viewpoints == other.viewpoints
200+
and self.bim_snippet == other.bim_snippet
201+
)
202+
if isinstance(other, TopicHandler)
203+
else NotImplemented
188204
)

0 commit comments

Comments
 (0)