mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 14:26:30 +00:00
fix: ifcclash bcfxml export
This commit is contained in:
committed by
Dion Moult
parent
4ee3458ff9
commit
a4566797fe
@@ -3,5 +3,5 @@ mypy
|
|||||||
pylint
|
pylint
|
||||||
isort
|
isort
|
||||||
xsdata
|
xsdata
|
||||||
tox
|
tox==3.27.1
|
||||||
tox-conda
|
tox-conda
|
||||||
|
|||||||
+32
-12
@@ -1,8 +1,10 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
|
|
||||||
def calc_camera_vectors(
|
def camera_vectors_from_element_placement(
|
||||||
elem_placement: NDArray[np.float_],
|
elem_placement: NDArray[np.float_],
|
||||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||||
"""
|
"""
|
||||||
@@ -15,19 +17,37 @@ def calc_camera_vectors(
|
|||||||
Camera position, direction and up vectors
|
Camera position, direction and up vectors
|
||||||
"""
|
"""
|
||||||
target_position = elem_placement[:3, 3]
|
target_position = elem_placement[:3, 3]
|
||||||
camera_position = target_position + np.array((5, 5, 5))
|
return camera_vectors_from_target_position(target_position)
|
||||||
camera_direction = unit_vector(camera_position - target_position)
|
|
||||||
|
|
||||||
|
def camera_vectors_from_target_position(
|
||||||
|
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
|
||||||
|
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||||
|
"""
|
||||||
|
Calculate the vectors of a camera pointing to a target point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_position: point the camera is pointing to.
|
||||||
|
camera_offset: offset of the camera from the target point.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Camera position, direction and up vectors
|
||||||
|
"""
|
||||||
|
camera_offset = np.array((5, 5, 5)) if offset is None else offset
|
||||||
|
camera_position = target_position + camera_offset
|
||||||
|
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||||
rotation_transform = np.eye(4)
|
return camera_position, camera_direction, camera_up
|
||||||
rotation_transform[0, :3] = camera_right
|
# rotation_transform = np.eye(4)
|
||||||
rotation_transform[1, :3] = camera_up
|
# rotation_transform[0, :3] = camera_right
|
||||||
rotation_transform[2, :3] = camera_direction
|
# rotation_transform[1, :3] = camera_up
|
||||||
translation_transform = np.eye(4)
|
# rotation_transform[2, :3] = camera_direction
|
||||||
translation_transform[:3, -1] = -camera_position
|
# translation_transform = np.eye(4)
|
||||||
look_at_transform = np.matmul(rotation_transform, translation_transform)
|
# translation_transform[:3, -1] = -camera_position
|
||||||
mat = np.linalg.inv(look_at_transform)
|
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||||
return camera_position, -mat[:3, 2], mat[:3, 1]
|
# mat = np.linalg.inv(look_at_transform)
|
||||||
|
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||||
|
|
||||||
|
|
||||||
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
import warnings
|
import warnings
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, TypeVar
|
from typing import Any, NoReturn, Optional, TypeVar
|
||||||
|
|
||||||
import bcf.v2.model as mdl
|
import bcf.v2.model as mdl
|
||||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||||
@@ -50,7 +50,7 @@ class BcfXml:
|
|||||||
self._version = (
|
self._version = (
|
||||||
self._xml_handler.parse(self._zip_file.read("bcf.version"), mdl.Version)
|
self._xml_handler.parse(self._zip_file.read("bcf.version"), mdl.Version)
|
||||||
if self._zip_file
|
if self._zip_file
|
||||||
else mdl.Version(version_id="3.0")
|
else mdl.Version(version_id="2.1")
|
||||||
)
|
)
|
||||||
return self._version
|
return self._version
|
||||||
|
|
||||||
@@ -210,10 +210,12 @@ class BcfXml:
|
|||||||
self.topics[topic_handler.guid] = topic_handler
|
self.topics[topic_handler.guid] = topic_handler
|
||||||
return topic_handler
|
return topic_handler
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool | NoReturn:
|
||||||
if not isinstance(other, BcfXml):
|
return (
|
||||||
raise TypeError("Equality needs a BcfXml object.")
|
self.version == other.version and self.project_info == other.project_info
|
||||||
return self.version == other.version and self.project_info == other.project_info
|
if isinstance(other, BcfXml)
|
||||||
|
else NotImplemented
|
||||||
|
)
|
||||||
|
|
||||||
# region Deprecated methods
|
# region Deprecated methods
|
||||||
def new_project(self) -> "BcfXml":
|
def new_project(self) -> "BcfXml":
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import datetime
|
|||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, NoReturn, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
from numpy.typing import NDArray
|
||||||
from xsdata.models.datatype import XmlDateTime
|
from xsdata.models.datatype import XmlDateTime
|
||||||
|
|
||||||
import bcf.v2.model as mdl
|
import bcf.v2.model as mdl
|
||||||
@@ -226,15 +228,29 @@ class TopicHandler:
|
|||||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||||
self.add_visinfo_handler(new_viewpoint)
|
self.add_visinfo_handler(new_viewpoint)
|
||||||
|
|
||||||
|
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||||
|
"""
|
||||||
|
Add a viewpoint tergeting an IFC element to the topic.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The IFC element.
|
||||||
|
"""
|
||||||
|
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||||
|
position, *guids, xml_handler=self._xml_handler
|
||||||
|
)
|
||||||
|
self.add_visinfo_handler(vi_handler)
|
||||||
|
|
||||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool | NoReturn:
|
||||||
if not isinstance(other, TopicHandler):
|
|
||||||
raise TypeError("Equality needs a BcfXml object.")
|
|
||||||
return (
|
return (
|
||||||
self.markup == other.markup
|
(
|
||||||
and self.viewpoints == other.viewpoints
|
self.markup == other.markup
|
||||||
and self.bim_snippet == other.bim_snippet
|
and self.viewpoints == other.viewpoints
|
||||||
|
and self.bim_snippet == other.bim_snippet
|
||||||
|
)
|
||||||
|
if isinstance(other, TopicHandler)
|
||||||
|
else NotImplemented
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
|
|||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
import bcf.v2.model as mdl
|
import bcf.v2.model as mdl
|
||||||
from bcf.geometry import calc_camera_vectors
|
from bcf.geometry import (
|
||||||
|
camera_vectors_from_element_placement,
|
||||||
|
camera_vectors_from_target_position,
|
||||||
|
)
|
||||||
from bcf.inmemory_zipfile import ZipFileInterface
|
from bcf.inmemory_zipfile import ZipFileInterface
|
||||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||||
|
|
||||||
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
|
|||||||
xml_handler = xml_handler or XmlParserSerializer()
|
xml_handler = xml_handler or XmlParserSerializer()
|
||||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_point_and_guids(
|
||||||
|
cls,
|
||||||
|
position: NDArray[np.float_],
|
||||||
|
*guids: str,
|
||||||
|
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||||
|
) -> "VisualizationInfoHandler":
|
||||||
|
"""
|
||||||
|
Create a new VisualizationInfoHandler object from an IFC element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: target point coordinates.
|
||||||
|
*guids: One or more IFC element GUID.
|
||||||
|
xml_handler: The XML handler to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The VisualizationInfoHandler object.
|
||||||
|
"""
|
||||||
|
xml_handler = xml_handler or XmlParserSerializer()
|
||||||
|
return cls(
|
||||||
|
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
@lru_cache(maxsize=None)
|
||||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||||
@@ -192,18 +218,39 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_components(guid: str) -> mdl.Components:
|
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||||
|
"""
|
||||||
|
Return a BCF viewpoint of an IFC element.
|
||||||
|
|
||||||
|
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: target point coordinates.
|
||||||
|
*guids: One or more IFC element GUID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The BCF viewpoint definition.
|
||||||
|
"""
|
||||||
|
return mdl.VisualizationInfo(
|
||||||
|
guid=str(uuid.uuid4()),
|
||||||
|
components=build_components(*guids),
|
||||||
|
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_components(*guids: str) -> mdl.Components:
|
||||||
"""
|
"""
|
||||||
Return the BCF components from an IFC element GUID.
|
Return the BCF components from an IFC element GUID.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
guid: The IFC element GUID.
|
*guids: One or more IFC element GUID.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The BCF components definition.
|
The BCF components definition.
|
||||||
"""
|
"""
|
||||||
|
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||||
return mdl.Components(
|
return mdl.Components(
|
||||||
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
|
selection=mdl.ComponentSelection(component=components),
|
||||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
|||||||
Returns:
|
Returns:
|
||||||
The BCF camera definition.
|
The BCF camera definition.
|
||||||
"""
|
"""
|
||||||
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
|
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||||
|
|
||||||
|
|
||||||
def build_camera_from_vectors(
|
def build_camera_from_vectors(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
import warnings
|
import warnings
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, TypeVar
|
from typing import Any, NoReturn, Optional, TypeVar
|
||||||
|
|
||||||
import bcf.v3.model as mdl
|
import bcf.v3.model as mdl
|
||||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||||
@@ -224,13 +224,13 @@ class BcfXml:
|
|||||||
self.topics[topic_handler.guid] = topic_handler
|
self.topics[topic_handler.guid] = topic_handler
|
||||||
return topic_handler
|
return topic_handler
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool | NoReturn:
|
||||||
if not isinstance(other, BcfXml):
|
|
||||||
raise TypeError("Equality needs a BcfXml object.")
|
|
||||||
return (
|
return (
|
||||||
self.version == other.version
|
self.version == other.version
|
||||||
and self.project_info == other.project_info
|
and self.project_info == other.project_info
|
||||||
and self.extensions == other.extensions
|
and self.extensions == other.extensions
|
||||||
|
if isinstance(other, BcfXml)
|
||||||
|
else NotImplemented
|
||||||
)
|
)
|
||||||
|
|
||||||
# region Deprecated methods
|
# region Deprecated methods
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import datetime
|
|||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, NoReturn, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
from numpy.typing import NDArray
|
||||||
from xsdata.models.datatype import XmlDateTime
|
from xsdata.models.datatype import XmlDateTime
|
||||||
|
|
||||||
import bcf.v3.model as mdl
|
import bcf.v3.model as mdl
|
||||||
@@ -73,7 +75,7 @@ class TopicHandler:
|
|||||||
self._bim_snippet = value
|
self._bim_snippet = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def viewpoints(self) -> Optional[VisualizationInfoHandler]:
|
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||||
if (
|
if (
|
||||||
not self._viewpoints
|
not self._viewpoints
|
||||||
and self._topic_dir
|
and self._topic_dir
|
||||||
@@ -143,7 +145,7 @@ class TopicHandler:
|
|||||||
self._save_viewpoints(destination_zip, topic_dir)
|
self._save_viewpoints(destination_zip, topic_dir)
|
||||||
self._save_bim_snippet(destination_zip)
|
self._save_bim_snippet(destination_zip)
|
||||||
|
|
||||||
def _save_viewpoints(self, destination_zip, topic_dir) -> None:
|
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||||
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
||||||
return
|
return
|
||||||
for vpt in viewpoints:
|
for vpt in viewpoints:
|
||||||
@@ -172,17 +174,31 @@ class TopicHandler:
|
|||||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||||
self.add_visinfo_handler(new_viewpoint)
|
self.add_visinfo_handler(new_viewpoint)
|
||||||
|
|
||||||
|
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||||
|
"""
|
||||||
|
Add a viewpoint tergeting an IFC element to the topic.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The IFC element.
|
||||||
|
"""
|
||||||
|
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||||
|
position, *guids, xml_handler=self._xml_handler
|
||||||
|
)
|
||||||
|
self.add_visinfo_handler(vi_handler)
|
||||||
|
|
||||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||||
if self.topic.viewpoints is None:
|
if self.topic.viewpoints is None:
|
||||||
self.topic.viewpoints = mdl.TopicViewpoints()
|
self.topic.viewpoints = mdl.TopicViewpoints()
|
||||||
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool | NoReturn:
|
||||||
if not isinstance(other, TopicHandler):
|
|
||||||
raise TypeError("Equality needs a BcfXml object.")
|
|
||||||
return (
|
return (
|
||||||
self.markup == other.markup
|
(
|
||||||
and self.viewpoints == other.viewpoints
|
self.markup == other.markup
|
||||||
and self.bim_snippet == other.bim_snippet
|
and self.viewpoints == other.viewpoints
|
||||||
|
and self.bim_snippet == other.bim_snippet
|
||||||
|
)
|
||||||
|
if isinstance(other, TopicHandler)
|
||||||
|
else NotImplemented
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
|
|||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
import bcf.v3.model as mdl
|
import bcf.v3.model as mdl
|
||||||
from bcf.geometry import calc_camera_vectors
|
from bcf.geometry import (
|
||||||
|
camera_vectors_from_element_placement,
|
||||||
|
camera_vectors_from_target_position,
|
||||||
|
)
|
||||||
from bcf.inmemory_zipfile import ZipFileInterface
|
from bcf.inmemory_zipfile import ZipFileInterface
|
||||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||||
|
|
||||||
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
|
|||||||
xml_handler = xml_handler or XmlParserSerializer()
|
xml_handler = xml_handler or XmlParserSerializer()
|
||||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_point_and_guids(
|
||||||
|
cls,
|
||||||
|
position: NDArray[np.float_],
|
||||||
|
*guids: str,
|
||||||
|
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||||
|
) -> "VisualizationInfoHandler":
|
||||||
|
"""
|
||||||
|
Create a new VisualizationInfoHandler object from an IFC element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: target point coordinates.
|
||||||
|
*guids: One or more IFC element GUID.
|
||||||
|
xml_handler: The XML handler to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The VisualizationInfoHandler object.
|
||||||
|
"""
|
||||||
|
xml_handler = xml_handler or XmlParserSerializer()
|
||||||
|
return cls(
|
||||||
|
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
@lru_cache(maxsize=None)
|
||||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||||
@@ -192,18 +218,39 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_components(guid: str) -> mdl.Components:
|
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||||
|
"""
|
||||||
|
Return a BCF viewpoint of an IFC element.
|
||||||
|
|
||||||
|
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: target point coordinates.
|
||||||
|
*guids: One or more IFC element GUID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The BCF viewpoint definition.
|
||||||
|
"""
|
||||||
|
return mdl.VisualizationInfo(
|
||||||
|
guid=str(uuid.uuid4()),
|
||||||
|
components=build_components(*guids),
|
||||||
|
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_components(*guids: str) -> mdl.Components:
|
||||||
"""
|
"""
|
||||||
Return the BCF components from an IFC element GUID.
|
Return the BCF components from an IFC element GUID.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
guid: The IFC element GUID.
|
*guids: One or more IFC element GUID.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The BCF components definition.
|
The BCF components definition.
|
||||||
"""
|
"""
|
||||||
|
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||||
return mdl.Components(
|
return mdl.Components(
|
||||||
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
|
selection=mdl.ComponentSelection(component=components),
|
||||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
|||||||
Returns:
|
Returns:
|
||||||
The BCF camera definition.
|
The BCF camera definition.
|
||||||
"""
|
"""
|
||||||
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
|
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||||
|
|
||||||
|
|
||||||
def build_camera_from_vectors(
|
def build_camera_from_vectors(
|
||||||
|
|||||||
@@ -47,12 +47,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
|||||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||||
parsed.save(modified_path)
|
parsed.save(modified_path)
|
||||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||||
assert modified_parsed == bcf
|
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
|
||||||
assert parsed_th.markup != orig_th.markup
|
|
||||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
assert modified_parsed == bcf
|
||||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||||
|
assert parsed_th.markup != orig_th.markup
|
||||||
|
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||||
|
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||||
|
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||||
|
|
||||||
|
|
||||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||||
@@ -66,13 +70,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
|
|||||||
th.topic.title = "New Topic Title"
|
th.topic.title = "New Topic Title"
|
||||||
parsed.save()
|
parsed.save()
|
||||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||||
assert modified_parsed == bcf
|
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||||
assert len(modified_parsed.topics) == 1
|
assert len(modified_parsed.topics) == 1
|
||||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
|
||||||
assert parsed_th.markup != orig_th.markup
|
|
||||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
|
||||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
|
||||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_no_filename(build_sample) -> None:
|
def test_save_no_filename(build_sample) -> None:
|
||||||
@@ -112,11 +111,16 @@ def test_massive_bcf(xml_handler) -> None:
|
|||||||
bcf.save(file_path)
|
bcf.save(file_path)
|
||||||
|
|
||||||
|
|
||||||
def test_equality_with_wrong_object() -> None:
|
def test_equality_with_wrong_object(build_sample) -> None:
|
||||||
with pytest.raises(TypeError):
|
assert build_sample[0] != "Wrong object"
|
||||||
build_sample[0] == "Wrong object"
|
|
||||||
|
|
||||||
|
|
||||||
def test_topic_equality_with_wrong_object() -> None:
|
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||||
with pytest.raises(TypeError):
|
assert build_sample[1] != "Wrong object"
|
||||||
build_sample[1] == "Wrong object"
|
|
||||||
|
|
||||||
|
def test_bcf_get_set_version(build_sample) -> None:
|
||||||
|
bcf = build_sample[0]
|
||||||
|
assert bcf.version.version_id == "2.1"
|
||||||
|
bcf.version.version_id = "2.0"
|
||||||
|
assert bcf.version.version_id == "2.0"
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bcf.v2.bcfxml import BcfXml
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_clash_set_bcf() -> None:
|
||||||
|
bcfxml = BcfXml.create_new("Clash Test")
|
||||||
|
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||||
|
topic.add_viewpoint_from_point_and_guids(
|
||||||
|
np.array([10, 10, 10]),
|
||||||
|
"firstId",
|
||||||
|
"secondId",
|
||||||
|
)
|
||||||
|
assert len(topic.viewpoints) == 1
|
||||||
|
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||||
|
v_info = vi_handler.visualization_info
|
||||||
|
assert v_info.guid == guid
|
||||||
|
components = v_info.components.selection.component
|
||||||
|
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||||
|
camera = v_info.perspective_camera
|
||||||
|
viewpoint = camera.camera_view_point
|
||||||
|
assert viewpoint.x == 15
|
||||||
|
assert viewpoint.y == 15
|
||||||
|
assert viewpoint.z == 15
|
||||||
|
# default direction is the unit vector of -1, -1, -1
|
||||||
|
direction = camera.camera_direction
|
||||||
|
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||||
|
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||||
|
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||||
|
# default
|
||||||
|
up_vector = camera.camera_up_vector
|
||||||
|
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||||
|
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||||
|
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||||
|
assert camera.field_of_view == 60
|
||||||
@@ -48,12 +48,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
|||||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||||
parsed.save(modified_path)
|
parsed.save(modified_path)
|
||||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||||
assert modified_parsed == bcf
|
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
|
||||||
assert parsed_th.markup != orig_th.markup
|
|
||||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
assert modified_parsed == bcf
|
||||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||||
|
assert parsed_th.markup != orig_th.markup
|
||||||
|
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||||
|
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||||
|
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||||
|
|
||||||
|
|
||||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||||
@@ -67,13 +71,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
|
|||||||
th.topic.title = "New Topic Title"
|
th.topic.title = "New Topic Title"
|
||||||
parsed.save()
|
parsed.save()
|
||||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||||
assert modified_parsed == bcf
|
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||||
assert len(modified_parsed.topics) == 1
|
assert len(modified_parsed.topics) == 1
|
||||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
|
||||||
assert parsed_th.markup != orig_th.markup
|
|
||||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
|
||||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
|
||||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_no_filename(build_sample) -> None:
|
def test_save_no_filename(build_sample) -> None:
|
||||||
@@ -88,7 +87,7 @@ def test_load_no_filename() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_save_keep_open(build_sample) -> None:
|
def test_save_keep_open(build_sample) -> None:
|
||||||
bcf, orig_th = build_sample
|
bcf, _ = build_sample
|
||||||
with TemporaryDirectory() as tmp_dir:
|
with TemporaryDirectory() as tmp_dir:
|
||||||
file_path = Path(tmp_dir) / "test.bcf"
|
file_path = Path(tmp_dir) / "test.bcf"
|
||||||
bcf.save(file_path, keep_open=True)
|
bcf.save(file_path, keep_open=True)
|
||||||
@@ -96,11 +95,6 @@ def test_save_keep_open(build_sample) -> None:
|
|||||||
bcf._zip_file.close()
|
bcf._zip_file.close()
|
||||||
|
|
||||||
|
|
||||||
# image = PIL.Image.new('RGB', size=(100, 100))
|
|
||||||
# file = BinaryIO()
|
|
||||||
# image.save(file)
|
|
||||||
|
|
||||||
|
|
||||||
def test_massive_bcf(xml_handler) -> None:
|
def test_massive_bcf(xml_handler) -> None:
|
||||||
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
||||||
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
||||||
@@ -119,11 +113,9 @@ def test_massive_bcf(xml_handler) -> None:
|
|||||||
bcf.save(file_path)
|
bcf.save(file_path)
|
||||||
|
|
||||||
|
|
||||||
def test_equality_with_wrong_object() -> None:
|
def test_equality_with_wrong_object(build_sample) -> None:
|
||||||
with pytest.raises(TypeError):
|
assert build_sample[0] != "Wrong object"
|
||||||
build_sample[0] == "Wrong object"
|
|
||||||
|
|
||||||
|
|
||||||
def test_topic_equality_with_wrong_object() -> None:
|
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||||
with pytest.raises(TypeError):
|
assert build_sample[1] != "Wrong object"
|
||||||
build_sample[1] == "Wrong object"
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bcf.v3.bcfxml import BcfXml
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_clash_set_bcf() -> None:
|
||||||
|
bcfxml = BcfXml.create_new("Clash Test")
|
||||||
|
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||||
|
topic.add_viewpoint_from_point_and_guids(
|
||||||
|
np.array([10, 10, 10]),
|
||||||
|
"firstId",
|
||||||
|
"secondId",
|
||||||
|
)
|
||||||
|
assert len(topic.viewpoints) == 1
|
||||||
|
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||||
|
v_info = vi_handler.visualization_info
|
||||||
|
assert v_info.guid == guid
|
||||||
|
components = v_info.components.selection.component
|
||||||
|
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||||
|
camera = v_info.perspective_camera
|
||||||
|
viewpoint = camera.camera_view_point
|
||||||
|
assert viewpoint.x == 15
|
||||||
|
assert viewpoint.y == 15
|
||||||
|
assert viewpoint.z == 15
|
||||||
|
# default direction is the unit vector of -1, -1, -1
|
||||||
|
direction = camera.camera_direction
|
||||||
|
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||||
|
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||||
|
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||||
|
# default
|
||||||
|
up_vector = camera.camera_up_vector
|
||||||
|
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||||
|
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||||
|
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||||
|
assert camera.field_of_view == 60
|
||||||
@@ -22,8 +22,6 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
@@ -117,71 +115,22 @@ class Clasher:
|
|||||||
self.export_json()
|
self.export_json()
|
||||||
|
|
||||||
def export_bcfxml(self):
|
def export_bcfxml(self):
|
||||||
import bcf
|
from bcf.v2.bcfxml import BcfXml
|
||||||
import bcf.v2.bcfxml
|
|
||||||
|
|
||||||
for i, clash_set in enumerate(self.clash_sets):
|
for i, clash_set in enumerate(self.clash_sets):
|
||||||
bcfxml = bcf.v2.bcfxml.BcfXml()
|
bcfxml = BcfXml.create_new(clash_set["name"])
|
||||||
bcfxml.new_project()
|
for clash in clash_set["clashes"].values():
|
||||||
bcfxml.project.name = clash_set["name"]
|
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
|
||||||
bcfxml.edit_project()
|
topic = bcfxml.add_topic(title, title, "IfcClash")
|
||||||
for key, clash in clash_set["clashes"].items():
|
topic.add_viewpoint_from_point_and_guids(
|
||||||
topic = bcf.v2.data.Topic()
|
np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
|
||||||
topic.title = "{}/{} and {}/{}".format(
|
|
||||||
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
|
|
||||||
)
|
)
|
||||||
topic = bcfxml.add_topic(topic)
|
suffix = f".{i}" if i else ""
|
||||||
viewpoint = bcf.v2.data.Viewpoint()
|
bcfxml.save_project(f"{self.settings.output}{suffix}")
|
||||||
viewpoint.perspective_camera = bcf.v2.data.PerspectiveCamera()
|
|
||||||
position = np.array(clash["position"])
|
|
||||||
point = position + np.array((5, 5, 5)) # Dumb, but works (for now)!
|
|
||||||
viewpoint.perspective_camera.camera_view_point.x = point[0]
|
|
||||||
viewpoint.perspective_camera.camera_view_point.y = point[1]
|
|
||||||
viewpoint.perspective_camera.camera_view_point.z = point[2]
|
|
||||||
mat = self.get_track_to_matrix(point, position)
|
|
||||||
viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1
|
|
||||||
viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1
|
|
||||||
viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1
|
|
||||||
viewpoint.perspective_camera.camera_up_vector.x = mat[0][1]
|
|
||||||
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
|
|
||||||
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
|
|
||||||
viewpoint.components = bcf.v2.data.Components()
|
|
||||||
c1 = bcf.v2.data.Component()
|
|
||||||
c1.ifc_guid = clash["a_global_id"]
|
|
||||||
c2 = bcf.v2.data.Component()
|
|
||||||
c2.ifc_guid = clash["b_global_id"]
|
|
||||||
viewpoint.components.selection.append(c1)
|
|
||||||
viewpoint.components.selection.append(c2)
|
|
||||||
viewpoint.components.visibility = bcf.v2.data.ComponentVisibility()
|
|
||||||
viewpoint.components.visibility.default_visibility = True
|
|
||||||
viewpoint.snapshot = self.get_viewpoint_snapshot(viewpoint, mat)
|
|
||||||
bcfxml.add_viewpoint(topic, viewpoint)
|
|
||||||
if i == 0:
|
|
||||||
bcfxml.save_project(self.settings.output)
|
|
||||||
else:
|
|
||||||
bcfxml.save_project(self.settings.output + f".{i}")
|
|
||||||
|
|
||||||
def get_viewpoint_snapshot(self, viewpoint, mat):
|
def get_viewpoint_snapshot(self, viewpoint, mat):
|
||||||
return None # Possible to overload this function in a GUI application if used as a library
|
return None # Possible to overload this function in a GUI application if used as a library
|
||||||
|
|
||||||
# https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706
|
|
||||||
def get_track_to_matrix(self, camera_position, target_position):
|
|
||||||
camera_direction = camera_position - target_position
|
|
||||||
camera_direction = camera_direction / np.linalg.norm(camera_direction)
|
|
||||||
camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction)
|
|
||||||
camera_right = camera_right / np.linalg.norm(camera_right)
|
|
||||||
camera_up = np.cross(camera_direction, camera_right)
|
|
||||||
camera_up = camera_up / np.linalg.norm(camera_up)
|
|
||||||
rotation_transform = np.zeros((4, 4))
|
|
||||||
rotation_transform[0, :3] = camera_right
|
|
||||||
rotation_transform[1, :3] = camera_up
|
|
||||||
rotation_transform[2, :3] = camera_direction
|
|
||||||
rotation_transform[-1, -1] = 1
|
|
||||||
translation_transform = np.eye(4)
|
|
||||||
translation_transform[:3, -1] = -camera_position
|
|
||||||
look_at_transform = np.matmul(rotation_transform, translation_transform)
|
|
||||||
return np.linalg.inv(look_at_transform)
|
|
||||||
|
|
||||||
def export_json(self):
|
def export_json(self):
|
||||||
clash_sets = self.clash_sets.copy()
|
clash_sets = self.clash_sets.copy()
|
||||||
for clash_set in clash_sets:
|
for clash_set in clash_sets:
|
||||||
|
|||||||
Reference in New Issue
Block a user