fix: ifcclash bcfxml export

This commit is contained in:
Andrea Ghensi
2023-01-14 15:49:23 +01:00
committed by Dion Moult
parent 4ee3458ff9
commit a4566797fe
13 changed files with 316 additions and 151 deletions
+1 -1
View File
@@ -3,5 +3,5 @@ mypy
pylint
isort
xsdata
tox
tox==3.27.1
tox-conda
+32 -12
View File
@@ -1,8 +1,10 @@
from typing import Optional
import numpy as np
from numpy.typing import NDArray
def calc_camera_vectors(
def camera_vectors_from_element_placement(
elem_placement: 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
"""
target_position = elem_placement[:3, 3]
camera_position = target_position + np.array((5, 5, 5))
camera_direction = unit_vector(camera_position - target_position)
return camera_vectors_from_target_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_up = unit_vector(np.cross(camera_direction, camera_right))
rotation_transform = np.eye(4)
rotation_transform[0, :3] = camera_right
rotation_transform[1, :3] = camera_up
rotation_transform[2, :3] = camera_direction
translation_transform = np.eye(4)
translation_transform[:3, -1] = -camera_position
look_at_transform = np.matmul(rotation_transform, translation_transform)
mat = np.linalg.inv(look_at_transform)
return camera_position, -mat[:3, 2], mat[:3, 1]
return camera_position, camera_direction, camera_up
# rotation_transform = np.eye(4)
# rotation_transform[0, :3] = camera_right
# rotation_transform[1, :3] = camera_up
# rotation_transform[2, :3] = camera_direction
# translation_transform = np.eye(4)
# translation_transform[:3, -1] = -camera_position
# look_at_transform = np.matmul(rotation_transform, translation_transform)
# 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_]:
+8 -6
View File
@@ -3,7 +3,7 @@ import uuid
import warnings
import zipfile
from pathlib import Path
from typing import Any, Optional, TypeVar
from typing import Any, NoReturn, Optional, TypeVar
import bcf.v2.model as mdl
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
@@ -50,7 +50,7 @@ class BcfXml:
self._version = (
self._xml_handler.parse(self._zip_file.read("bcf.version"), mdl.Version)
if self._zip_file
else mdl.Version(version_id="3.0")
else mdl.Version(version_id="2.1")
)
return self._version
@@ -210,10 +210,12 @@ class BcfXml:
self.topics[topic_handler.guid] = topic_handler
return topic_handler
def __eq__(self, other: object) -> bool:
if not isinstance(other, BcfXml):
raise TypeError("Equality needs a BcfXml object.")
return self.version == other.version and self.project_info == other.project_info
def __eq__(self, other: object) -> bool | NoReturn:
return (
self.version == other.version and self.project_info == other.project_info
if isinstance(other, BcfXml)
else NotImplemented
)
# region Deprecated methods
def new_project(self) -> "BcfXml":
+23 -7
View File
@@ -3,9 +3,11 @@ import datetime
import uuid
import zipfile
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 numpy.typing import NDArray
from xsdata.models.datatype import XmlDateTime
import bcf.v2.model as mdl
@@ -226,15 +228,29 @@ class TopicHandler:
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
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:
self.viewpoints[new_viewpoint.guid] = new_viewpoint
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
def __eq__(self, other: object) -> bool:
if not isinstance(other, TopicHandler):
raise TypeError("Equality needs a BcfXml object.")
def __eq__(self, other: object) -> bool | NoReturn:
return (
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
(
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
if isinstance(other, TopicHandler)
else NotImplemented
)
+52 -5
View File
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
from numpy.typing import NDArray
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.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
xml_handler = xml_handler or XmlParserSerializer()
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)
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.
Args:
guid: The IFC element GUID.
*guids: One or more IFC element GUID.
Returns:
The BCF components definition.
"""
components = [mdl.Component(ifc_guid=guid) for guid in guids]
return mdl.Components(
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
selection=mdl.ComponentSelection(component=components),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
Returns:
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(
+4 -4
View File
@@ -3,7 +3,7 @@ import uuid
import warnings
import zipfile
from pathlib import Path
from typing import Any, Optional, TypeVar
from typing import Any, NoReturn, Optional, TypeVar
import bcf.v3.model as mdl
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
@@ -224,13 +224,13 @@ class BcfXml:
self.topics[topic_handler.guid] = topic_handler
return topic_handler
def __eq__(self, other: object) -> bool:
if not isinstance(other, BcfXml):
raise TypeError("Equality needs a BcfXml object.")
def __eq__(self, other: object) -> bool | NoReturn:
return (
self.version == other.version
and self.project_info == other.project_info
and self.extensions == other.extensions
if isinstance(other, BcfXml)
else NotImplemented
)
# region Deprecated methods
+25 -9
View File
@@ -3,9 +3,11 @@ import datetime
import uuid
import zipfile
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 numpy.typing import NDArray
from xsdata.models.datatype import XmlDateTime
import bcf.v3.model as mdl
@@ -73,7 +75,7 @@ class TopicHandler:
self._bim_snippet = value
@property
def viewpoints(self) -> Optional[VisualizationInfoHandler]:
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
if (
not self._viewpoints
and self._topic_dir
@@ -143,7 +145,7 @@ class TopicHandler:
self._save_viewpoints(destination_zip, topic_dir)
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):
return
for vpt in viewpoints:
@@ -172,17 +174,31 @@ class TopicHandler:
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
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:
self.viewpoints[new_viewpoint.guid] = new_viewpoint
if self.topic.viewpoints is None:
self.topic.viewpoints = mdl.TopicViewpoints()
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
def __eq__(self, other: object) -> bool:
if not isinstance(other, TopicHandler):
raise TypeError("Equality needs a BcfXml object.")
def __eq__(self, other: object) -> bool | NoReturn:
return (
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
(
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
if isinstance(other, TopicHandler)
else NotImplemented
)
+52 -5
View File
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
from numpy.typing import NDArray
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.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
xml_handler = xml_handler or XmlParserSerializer()
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)
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.
Args:
guid: The IFC element GUID.
*guids: One or more IFC element GUID.
Returns:
The BCF components definition.
"""
components = [mdl.Component(ifc_guid=guid) for guid in guids]
return mdl.Components(
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
selection=mdl.ComponentSelection(component=components),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
Returns:
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(
+22 -18
View File
@@ -47,12 +47,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
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
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
assert modified_parsed == bcf
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:
@@ -66,13 +70,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
th.topic.title = "New Topic Title"
parsed.save()
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
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:
@@ -112,11 +111,16 @@ def test_massive_bcf(xml_handler) -> None:
bcf.save(file_path)
def test_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[0] == "Wrong object"
def test_equality_with_wrong_object(build_sample) -> None:
assert build_sample[0] != "Wrong object"
def test_topic_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[1] == "Wrong object"
def test_topic_equality_with_wrong_object(build_sample) -> None:
assert 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"
+36
View File
@@ -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
+16 -24
View File
@@ -48,12 +48,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
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
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
assert modified_parsed == bcf
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:
@@ -67,13 +71,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
th.topic.title = "New Topic Title"
parsed.save()
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
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:
@@ -88,7 +87,7 @@ def test_load_no_filename() -> None:
def test_save_keep_open(build_sample) -> None:
bcf, orig_th = build_sample
bcf, _ = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path, keep_open=True)
@@ -96,11 +95,6 @@ def test_save_keep_open(build_sample) -> None:
bcf._zip_file.close()
# image = PIL.Image.new('RGB', size=(100, 100))
# file = BinaryIO()
# image.save(file)
def test_massive_bcf(xml_handler) -> None:
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
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)
def test_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[0] == "Wrong object"
def test_equality_with_wrong_object(build_sample) -> None:
assert build_sample[0] != "Wrong object"
def test_topic_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[1] == "Wrong object"
def test_topic_equality_with_wrong_object(build_sample) -> None:
assert build_sample[1] != "Wrong object"
+36
View File
@@ -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
+9 -60
View File
@@ -22,8 +22,6 @@
import numpy as np
import json
import sys
import argparse
import logging
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
@@ -117,71 +115,22 @@ class Clasher:
self.export_json()
def export_bcfxml(self):
import bcf
import bcf.v2.bcfxml
from bcf.v2.bcfxml import BcfXml
for i, clash_set in enumerate(self.clash_sets):
bcfxml = bcf.v2.bcfxml.BcfXml()
bcfxml.new_project()
bcfxml.project.name = clash_set["name"]
bcfxml.edit_project()
for key, clash in clash_set["clashes"].items():
topic = bcf.v2.data.Topic()
topic.title = "{}/{} and {}/{}".format(
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
bcfxml = BcfXml.create_new(clash_set["name"])
for clash in clash_set["clashes"].values():
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
topic = bcfxml.add_topic(title, title, "IfcClash")
topic.add_viewpoint_from_point_and_guids(
np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
)
topic = bcfxml.add_topic(topic)
viewpoint = bcf.v2.data.Viewpoint()
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}")
suffix = f".{i}" if i else ""
bcfxml.save_project(f"{self.settings.output}{suffix}")
def get_viewpoint_snapshot(self, viewpoint, mat):
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):
clash_sets = self.clash_sets.copy()
for clash_set in clash_sets: