mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-14 03:14:23 +00:00
[BCF] New BCF XML handler with better performances (#2355)
BREAKING CHANGE: incompatible methods and objects with previous version. closes #2321
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""BCF tests."""
|
||||
@@ -0,0 +1,8 @@
|
||||
import pytest
|
||||
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def xml_handler() -> XmlParserSerializer:
|
||||
return XmlParserSerializer()
|
||||
Binary file not shown.
@@ -0,0 +1,122 @@
|
||||
"""BCF XML tests."""
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from bcf.v2.visinfo import (
|
||||
VisualizationInfoHandler,
|
||||
build_camera_from_vectors,
|
||||
build_components,
|
||||
)
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
|
||||
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
|
||||
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
|
||||
return bcf, orig_th
|
||||
|
||||
|
||||
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
assert parsed == bcf
|
||||
parsed_th = parsed.topics[orig_th.guid]
|
||||
assert parsed_th == orig_th
|
||||
|
||||
|
||||
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
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
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
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 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:
|
||||
bcf, _ = build_sample
|
||||
with pytest.raises(ValueError):
|
||||
bcf.save()
|
||||
|
||||
|
||||
def test_load_no_filename() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
BcfXml.load("")
|
||||
|
||||
|
||||
def test_save_keep_open(build_sample) -> None:
|
||||
bcf, _ = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path, keep_open=True)
|
||||
assert bcf._zip_file is not None
|
||||
bcf._zip_file.close()
|
||||
|
||||
|
||||
def test_massive_bcf(xml_handler) -> None:
|
||||
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
|
||||
for i in range(100):
|
||||
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
|
||||
vi = mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(str(uuid.uuid4())),
|
||||
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
|
||||
)
|
||||
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
|
||||
th.add_visinfo_handler(vh)
|
||||
assert len(bcf.topics) == 100
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[0] == "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[1] == "Wrong object"
|
||||
@@ -0,0 +1,365 @@
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
|
||||
|
||||
def test_maximum_information() -> None:
|
||||
"""
|
||||
All the info in a BCF is parsed correctly.
|
||||
|
||||
Uses sample file from https://github.com/buildingSMART/BCF-XML/
|
||||
"""
|
||||
bcf_path = Path(__file__).parent / "MaximumInformation.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
assert_everything_in_place(bcf)
|
||||
|
||||
|
||||
def test_save_maximum_information() -> None:
|
||||
base_dir = Path(__file__).parent
|
||||
bcf_path = base_dir / "MaximumInformation.bcf"
|
||||
target_path = base_dir / "MaximumInformationSaved.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
bcf.save(target_path)
|
||||
assert target_path.exists()
|
||||
with BcfXml.load(target_path) as bcf2:
|
||||
assert_everything_in_place(bcf2)
|
||||
|
||||
expected_files = [
|
||||
"bcf.version",
|
||||
"project.bcfp",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/markup.bcf",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/bitmap.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/tux.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/JsonElement.json",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
|
||||
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
|
||||
"d1068c81-af04-4546-b63c-348810f6c716/markup.bcf",
|
||||
"extensions.xsd",
|
||||
"IfcPile_01.ifc",
|
||||
"markup.xsd",
|
||||
]
|
||||
assert_files_present(target_path, expected_files)
|
||||
os.unlink(target_path)
|
||||
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "2.1"
|
||||
assert bcf.project.name == "BCF API Implementation"
|
||||
assert bcf.project_info.extension_schema == "extensions.xsd"
|
||||
|
||||
assert len(bcf.topics) == 2
|
||||
assert_first_topic_handler(bcf.topics["7ddc3ef0-0ab7-43f1-918a-45e38b42369c"])
|
||||
second_th = bcf.topics["d1068c81-af04-4546-b63c-348810f6c716"]
|
||||
assert second_th.topic == mdl.Topic(
|
||||
title="Referenced topic",
|
||||
creation_date=XmlDateTime(2017, 5, 22, 7, 51, 0, 42987900),
|
||||
creation_author="dangl@iabi.eu",
|
||||
description="This is just an empty topic that acts as a referenced topic.",
|
||||
guid="d1068c81-af04-4546-b63c-348810f6c716",
|
||||
)
|
||||
|
||||
|
||||
def assert_first_topic_handler(topic_handler: TopicHandler):
|
||||
assert topic_handler.guid == "7ddc3ef0-0ab7-43f1-918a-45e38b42369c"
|
||||
|
||||
expected_bs1 = mdl.BimSnippet(
|
||||
reference="JsonElement.json", reference_schema="http://json-schema.org", snippet_type="JSON"
|
||||
)
|
||||
expected_t1 = mdl.Topic(
|
||||
reference_link=["https://bim--it.net"],
|
||||
title="Maximum Content",
|
||||
priority="High",
|
||||
index=0,
|
||||
labels=["Structural", "IT Development"],
|
||||
creation_date=XmlDateTime(2015, 6, 21, 12, 0, 0),
|
||||
creation_author="dangl@iabi.eu",
|
||||
modified_date=XmlDateTime(2015, 6, 21, 14, 22, 47),
|
||||
modified_author="dangl@iabi.eu",
|
||||
due_date=XmlDateTime(2016, 10, 2, 14, 22, 47),
|
||||
assigned_to="linhard@iabi.eu",
|
||||
stage="Construction Start",
|
||||
description="This is a topic with all informations present.",
|
||||
bim_snippet=expected_bs1,
|
||||
document_reference=[
|
||||
mdl.TopicDocumentReference(
|
||||
referenced_document="https://github.com/BuildingSMART/BCF-XML",
|
||||
description="GitHub BCF Specification",
|
||||
is_external=True,
|
||||
),
|
||||
mdl.TopicDocumentReference(
|
||||
referenced_document="../markup.xsd",
|
||||
description="Markup.xsd Schema",
|
||||
is_external=False,
|
||||
),
|
||||
],
|
||||
related_topic=[mdl.TopicRelatedTopic(guid="d1068c81-af04-4546-b63c-348810f6c716")],
|
||||
guid="7ddc3ef0-0ab7-43f1-918a-45e38b42369c",
|
||||
topic_type="Structural",
|
||||
topic_status="Open",
|
||||
)
|
||||
assert topic_handler.topic == expected_t1
|
||||
expected_h1 = mdl.Header(
|
||||
file=[
|
||||
mdl.HeaderFile(
|
||||
filename="IfcPile_01.ifc",
|
||||
date=XmlDateTime(2014, 10, 27, 16, 27, 27),
|
||||
reference="../IfcPile_01.ifc",
|
||||
ifc_project="0M6o7Znnv7hxsbWgeu7oQq",
|
||||
ifc_spatial_structure_element="23B$bNeGHFQuMYJzvUX0FD",
|
||||
is_external=False,
|
||||
)
|
||||
]
|
||||
)
|
||||
assert topic_handler.header == expected_h1
|
||||
|
||||
expected_th1_comments = [
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 12, 40, 17),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This is an unmodified topic at the uppermost hierarchical level.\nAll times in the XML are marked as UTC times.",
|
||||
guid="07ccdba0-1736-47e1-807d-67dc6f3addaa",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 14, 0, 1),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.",
|
||||
guid="a12766c2-61bc-40b4-ab19-e9f45fd0b0bf",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 13, 7, 11),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment again is in the highest hierarchy level.\nIt references a viewpoint.",
|
||||
viewpoint=mdl.CommentViewpoint(guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a"),
|
||||
guid="c2bb5bb0-773d-45dd-bdaa-19a216439ed3",
|
||||
),
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2015, 8, 31, 15, 42, 58),
|
||||
author="dangl@iabi.eu",
|
||||
comment="This comment contained some spllng errs.\nHopefully, the modifier did catch them all.",
|
||||
modified_date=XmlDateTime(2015, 8, 31, 16, 7, 11),
|
||||
modified_author="dangl@iabi.eu",
|
||||
guid="0b843a5c-c3bf-41ef-be98-52a9f7bd9790",
|
||||
),
|
||||
]
|
||||
assert topic_handler.comments == expected_th1_comments
|
||||
|
||||
expected_th1_viewpoints = [
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
|
||||
snapshot="Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
|
||||
index=2,
|
||||
guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a",
|
||||
),
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
|
||||
snapshot="Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
|
||||
index=0,
|
||||
guid="fc4019d7-365e-47f3-b6d0-b39fc48f15fc",
|
||||
),
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
|
||||
snapshot="Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
|
||||
index=1,
|
||||
guid="9a4a1878-ecbd-4916-83a8-dad82e560231",
|
||||
),
|
||||
]
|
||||
|
||||
expected_m1 = mdl.Markup(
|
||||
header=expected_h1,
|
||||
topic=expected_t1,
|
||||
comment=expected_th1_comments,
|
||||
viewpoints=expected_th1_viewpoints,
|
||||
)
|
||||
assert topic_handler.markup == expected_m1
|
||||
|
||||
assert json.loads(topic_handler.bim_snippet) == {"Material": "Concrete", "Temperatures": ["Cold", "Hot", "Hotter"]}
|
||||
|
||||
assert topic_handler.reference_files["../IfcPile_01.ifc"] is not None
|
||||
|
||||
assert_viewpoints(topic_handler.viewpoints)
|
||||
|
||||
|
||||
def assert_viewpoints(viewpoints):
|
||||
assert len(viewpoints) == 3
|
||||
|
||||
expected_selection = mdl.ComponentSelection(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
|
||||
mdl.Component(ifc_guid="1jQQiGIAnFzxOUzrdmJYDS"),
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
]
|
||||
)
|
||||
|
||||
expected_exception = mdl.ComponentVisibilityExceptions(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0Gl71cVurFn8bxAOox6M4X"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="3DvyPxGIn8qR0KDwbL_9r1"),
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
]
|
||||
)
|
||||
|
||||
expected_coloring = mdl.ComponentColoring(
|
||||
color=[
|
||||
mdl.ComponentColoringColor(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
|
||||
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
|
||||
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
|
||||
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
|
||||
],
|
||||
color="3498DB",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
assert_first_viewpoint(
|
||||
viewpoints["Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
assert_second_viewpoint(
|
||||
viewpoints["Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
assert_third_viewpoint(
|
||||
viewpoints["Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv"],
|
||||
expected_selection,
|
||||
expected_exception,
|
||||
expected_coloring,
|
||||
)
|
||||
|
||||
|
||||
def assert_first_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=True,
|
||||
space_boundaries_visible=True,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=0.43079984188079834, y=69.52057647705078, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.09159398823976517, y=-0.9375035166740417, z=-0.3357048034667969),
|
||||
camera_up_vector=mdl.Direction(x=0.01938679628074169, y=-0.3353792130947113, z=0.9418837428092957),
|
||||
field_of_view=60,
|
||||
),
|
||||
lines=mdl.VisualizationInfoLines(
|
||||
line=[
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=0, z=0), end_point=mdl.Point(x=0, y=0, z=1)),
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=0, z=1), end_point=mdl.Point(x=0, y=1, z=1)),
|
||||
mdl.Line(start_point=mdl.Point(x=0, y=1, z=1), end_point=mdl.Point(x=1, y=1, z=1)),
|
||||
]
|
||||
),
|
||||
clipping_planes=mdl.VisualizationInfoClippingPlanes(
|
||||
clipping_plane=[
|
||||
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=0, z=1)),
|
||||
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=1, z=0)),
|
||||
]
|
||||
),
|
||||
bitmap=[
|
||||
mdl.VisualizationInfoBitmap(
|
||||
bitmap=mdl.BitmapFormat.PNG,
|
||||
reference="bitmap.png",
|
||||
location=mdl.Point(x=10, y=-10, z=7),
|
||||
normal=mdl.Direction(x=0, y=1, z=0),
|
||||
up=mdl.Direction(x=0, y=0, z=1),
|
||||
height=5.0,
|
||||
),
|
||||
mdl.VisualizationInfoBitmap(
|
||||
bitmap=mdl.BitmapFormat.PNG,
|
||||
reference="tux.png",
|
||||
location=mdl.Point(x=20, y=-10, z=7),
|
||||
normal=mdl.Direction(x=0, y=1, z=0),
|
||||
up=mdl.Direction(x=0, y=0, z=1),
|
||||
height=5.0,
|
||||
),
|
||||
],
|
||||
guid="8dc86298-9737-40b4-a448-98a9e953293a",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=False,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
||||
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
||||
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_files_present(saved_bcf_path, expected_files):
|
||||
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
|
||||
for file_path in expected_files:
|
||||
assert zipfile.Path(bcf_zip, file_path).exists()
|
||||
Binary file not shown.
@@ -0,0 +1,129 @@
|
||||
"""BCF XML tests."""
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
from bcf.v3.visinfo import (
|
||||
VisualizationInfoHandler,
|
||||
build_camera_from_vectors,
|
||||
build_components,
|
||||
)
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
|
||||
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
||||
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
||||
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
|
||||
return bcf, orig_th
|
||||
|
||||
|
||||
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
assert parsed == bcf
|
||||
parsed_th = parsed.topics[orig_th.guid]
|
||||
assert parsed_th == orig_th
|
||||
|
||||
|
||||
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
th.topic.title = "New Topic Title"
|
||||
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
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
"""Saving and loading a bcf xml project returns the same objects."""
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
|
||||
for th in parsed.topics.values():
|
||||
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 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:
|
||||
bcf, _ = build_sample
|
||||
with pytest.raises(ValueError):
|
||||
bcf.save()
|
||||
|
||||
|
||||
def test_load_no_filename() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
BcfXml.load("")
|
||||
|
||||
|
||||
def test_save_keep_open(build_sample) -> None:
|
||||
bcf, orig_th = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path, keep_open=True)
|
||||
assert bcf._zip_file is not 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)
|
||||
for i in range(100):
|
||||
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
|
||||
vi = mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(str(uuid.uuid4())),
|
||||
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
|
||||
)
|
||||
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
|
||||
th.add_visinfo_handler(vh)
|
||||
assert len(bcf.topics) == 100
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[0] == "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[1] == "Wrong object"
|
||||
@@ -0,0 +1,230 @@
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
|
||||
|
||||
def test_doc_ref_internal() -> None:
|
||||
"""
|
||||
All the info in a BCF is parsed correctly.
|
||||
|
||||
Uses sample file from https://github.com/buildingSMART/BCF-XML/
|
||||
"""
|
||||
bcf_path = Path(__file__).parent / "Document reference internal.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
assert_everything_in_place(bcf)
|
||||
|
||||
|
||||
def test_doc_ref_internal_save() -> None:
|
||||
base_dir = Path(__file__).parent
|
||||
bcf_path = base_dir / "Document reference internal.bcf"
|
||||
target_path = base_dir / "Document reference internal Saved.bcf"
|
||||
with BcfXml.load(bcf_path) as bcf:
|
||||
bcf.save(target_path)
|
||||
assert target_path.exists()
|
||||
with BcfXml.load(target_path) as bcf2:
|
||||
assert_everything_in_place(bcf2)
|
||||
assert_files_present(target_path)
|
||||
os.unlink(target_path)
|
||||
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "3.0"
|
||||
assert bcf.project.name == "BCF 3.0 test cases"
|
||||
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
|
||||
|
||||
assert len(bcf.topics) == 1
|
||||
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
|
||||
|
||||
expected_h1 = mdl.Header(
|
||||
files=mdl.HeaderFiles(
|
||||
file=[
|
||||
mdl.File(
|
||||
filename="BCF-ARK",
|
||||
date=XmlDateTime(2021, 1, 4, 9, 37, 45),
|
||||
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/007afab57f264d2296aae0a452486ae1",
|
||||
is_external=True,
|
||||
),
|
||||
mdl.File(
|
||||
filename="BCF-MEP",
|
||||
date=XmlDateTime(2017, 8, 7, 9, 51, 34),
|
||||
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/a21ed391f9e046a2bb2bc879c48f1d48",
|
||||
is_external=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
assert topic_handler.header == expected_h1
|
||||
|
||||
expected_comments = [
|
||||
mdl.Comment(
|
||||
date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
author="Architect@example.com",
|
||||
comment="A comment",
|
||||
viewpoint=mdl.CommentViewpoint(guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182"),
|
||||
guid="c045ef04-324d-4c36-9ac8-831f2a15c6d6",
|
||||
),
|
||||
]
|
||||
assert topic_handler.comments == expected_comments
|
||||
|
||||
expected_viewpoints = [
|
||||
mdl.ViewPoint(
|
||||
viewpoint="Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
|
||||
snapshot="Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
|
||||
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
|
||||
),
|
||||
]
|
||||
|
||||
expected_topic = mdl.Topic(
|
||||
reference_links=mdl.TopicReferenceLinks(),
|
||||
title="Document Reference Internal",
|
||||
labels=mdl.TopicLabels(),
|
||||
creation_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
creation_author="Architect@example.com",
|
||||
modified_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
|
||||
assigned_to="OtherUser@doe.com",
|
||||
document_references=mdl.TopicDocumentReferences(
|
||||
document_reference=[
|
||||
mdl.DocumentReference(
|
||||
document_guid="b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
|
||||
description="ThisIsADocument.txt",
|
||||
guid="048e898f-555f-47c3-a273-6c664fb2ef69",
|
||||
),
|
||||
],
|
||||
),
|
||||
related_topics=mdl.TopicRelatedTopics(),
|
||||
comments=mdl.TopicComments(comment=expected_comments),
|
||||
viewpoints=mdl.TopicViewpoints(view_point=expected_viewpoints),
|
||||
guid="8ac9822a-761a-4deb-9f39-f61286acbf6a",
|
||||
server_assigned_id="6",
|
||||
topic_type="ERROR",
|
||||
topic_status="OPEN",
|
||||
)
|
||||
assert topic_handler.topic == expected_topic
|
||||
|
||||
expected_m1 = mdl.Markup(
|
||||
header=expected_h1,
|
||||
topic=expected_topic,
|
||||
)
|
||||
assert topic_handler.markup == expected_m1
|
||||
|
||||
assert topic_handler.bim_snippet is None
|
||||
|
||||
assert_viewpoints(topic_handler.viewpoints)
|
||||
|
||||
|
||||
def assert_viewpoints(viewpoints):
|
||||
assert len(viewpoints) == 1
|
||||
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
selection=mdl.ComponentSelection(),
|
||||
visibility=mdl.ComponentVisibility(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
exceptions=mdl.ComponentVisibilityExceptions(
|
||||
component=[
|
||||
mdl.Component(ifc_guid="1m5wAJelDFdhn6qBdOGjos", originating_system="https://bimsync.com"),
|
||||
mdl.Component(ifc_guid="1bbI761TbBCOoIa5Kt6PXt", originating_system="https://bimsync.com"),
|
||||
]
|
||||
),
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=mdl.ComponentColoring(),
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=23.112083480037754, y=-25.45574897560043, z=18.52519828443092),
|
||||
camera_direction=mdl.Direction(x=-0.6289237666876837, y=0.5933487270610425, z=-0.5023864884632315),
|
||||
camera_up_vector=mdl.Direction(x=-0.36542566067664123, y=0.3447553774917179, z=0.8646431727652648),
|
||||
field_of_view=60,
|
||||
aspect_ratio=1,
|
||||
),
|
||||
lines=mdl.VisualizationInfoLines(),
|
||||
clipping_planes=mdl.VisualizationInfoClippingPlanes(),
|
||||
bitmaps=mdl.VisualizationInfoBitmaps(),
|
||||
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
|
||||
)
|
||||
|
||||
viewpoint = viewpoints["Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv"]
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=False,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=False,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
||||
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||
expected_vp = mdl.VisualizationInfo(
|
||||
components=mdl.Components(
|
||||
view_setup_hints=mdl.ViewSetupHints(
|
||||
spaces_visible=False,
|
||||
space_boundaries_visible=False,
|
||||
openings_visible=True,
|
||||
),
|
||||
selection=expected_selection,
|
||||
visibility=mdl.ComponentVisibility(
|
||||
exceptions=expected_exception,
|
||||
default_visibility=True,
|
||||
),
|
||||
coloring=expected_coloring,
|
||||
),
|
||||
perspective_camera=mdl.PerspectiveCamera(
|
||||
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
|
||||
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
||||
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
||||
field_of_view=60,
|
||||
),
|
||||
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
||||
)
|
||||
assert viewpoint.visualization_info == expected_vp
|
||||
assert viewpoint.snapshot is not None
|
||||
|
||||
|
||||
def assert_files_present(saved_bcf_path):
|
||||
expected_files = [
|
||||
"bcf.version",
|
||||
"project.bcfp",
|
||||
"documents.xml",
|
||||
"extensions.xml",
|
||||
"documents/b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/markup.bcf",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
|
||||
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
|
||||
for file_path in expected_files:
|
||||
assert zipfile.Path(bcf_zip, file_path).exists()
|
||||
Reference in New Issue
Block a user