diff --git a/.gitignore b/.gitignore
index 29a0f337d9..2d0f1d3b67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,10 @@ __pycache__
# PyCharm files
.idea
+#Virtual Env Files
+Pipfile
+Pipfile.lock
+
# Docs
/docs/output
/docs/rst_files
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 0907cf9739..114671a4e6 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -280,7 +280,7 @@ ENDIF()
# Use the found libTKernel as a template for all other OCC libraries
# TODO Extract this into macro/function
foreach(lib ${OPENCASCADE_LIBRARY_NAMES})
- # Make sure we'll handle the Windows/MSVC debug postfix convetion too.
+ # Make sure we'll handle the Windows/MSVC debug postfix convention too.
string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}")
string(REPLACE TKernel "${lib}" lib_path "${lib_path}")
list(APPEND OPENCASCADE_LIBRARIES "${lib_path}")
@@ -355,7 +355,7 @@ IF(COLLADA_SUPPORT AND BUILD_CONVERT)
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
- # Make sure we'll handle the Windows/MSVC debug postfix convetion too.
+ # Make sure we'll handle the Windows/MSVC debug postfix convention too.
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
@@ -496,6 +496,10 @@ endfunction()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
+foreach(s ${SCHEMA_VERSIONS})
+ add_definitions(-DHAS_SCHEMA_${s})
+endforeach()
+
if(COMPILE_SCHEMA)
# @todo, this appears to be untested at the moment
@@ -573,8 +577,35 @@ if (BUILD_CONVERT)
endif()
# IfcParse
-file(GLOB IFCPARSE_H_FILES ../src/ifcparse/*.h)
-file(GLOB IFCPARSE_CPP_FILES ../src/ifcparse/*.cpp)
+file(GLOB IFCPARSE_H_FILES_ALL ../src/ifcparse/*.h)
+file(GLOB IFCPARSE_CPP_FILES_ALL ../src/ifcparse/*.cpp)
+
+foreach(s ${IFCPARSE_H_FILES_ALL})
+get_filename_component(p "${s}" NAME)
+if (NOT "${p}" MATCHES "[0-9]")
+list(APPEND IFCPARSE_H_FILES "${s}")
+endif()
+endforeach()
+
+foreach(s ${IFCPARSE_CPP_FILES_ALL})
+get_filename_component(p "${s}" NAME)
+if (NOT "${p}" MATCHES "[0-9]")
+list(APPEND IFCPARSE_CPP_FILES "${s}")
+endif()
+endforeach()
+
+foreach(s ${SCHEMA_VERSIONS})
+ list(APPEND IFCPARSE_H_FILES
+ ../src/ifcparse/Ifc${s}.h
+ ../src/ifcparse/Ifc${s}-definitions.h
+ )
+ list(APPEND IFCPARSE_CPP_FILES
+ ../src/ifcparse/Ifc${s}.cpp
+ ../src/ifcparse/Ifc${s}-schema.cpp
+ )
+endforeach()
+
+
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
add_library(IfcParse ${IFCPARSE_FILES})
diff --git a/nix/build-all.py b/nix/build-all.py
index 0871e52633..efd0983b4a 100644
--- a/nix/build-all.py
+++ b/nix/build-all.py
@@ -310,7 +310,7 @@ def run(cmds, cwd=None):
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
-BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,)
+BOOST_LOCATION="https://boostorg.jfrog.io/artifactory/main/release/%s/source/" % (BOOST_VERSION,)
# Helper functions
diff --git a/src/bcf/README.md b/src/bcf/README.md
index 90c3fd71ca..1ce991d97f 100644
--- a/src/bcf/README.md
+++ b/src/bcf/README.md
@@ -4,23 +4,26 @@ A simple Python implementation of BCF. The data model is described in `data.py`.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
-Currently supports BCF version 2.1.
+ - BCF-XML version 2.1: Fully supported
+ - BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
+ - BCF-XML version 3.0: Almost fully supported, except for the documents module
+ - BCF-API version 3.0: Not supported, but work underway to support it
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
```
-from bcf.bcfxml import BcfXml
+from bcf import bcfxml
-bcfxml = BcfXml()
# Load a project
-project = bcfxml.get_project("/path/to/file.bcf")
+bcfxml = bcfxml.load("/path/to/file.bcf")
+
# The project is also stored in the module
# project == bcfxml.project
-
+project=bcfxml.get_project()
print(project.name)
# To edit a project, just modify the object directly
diff --git a/src/bcf/bcf/__init__.py b/src/bcf/bcf/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py
index 13885b7f7d..28b25a8db1 100644
--- a/src/bcf/bcf/bcfxml.py
+++ b/src/bcf/bcf/bcfxml.py
@@ -1,765 +1,39 @@
-import os
-import uuid
-import shutil
+import os.path
import zipfile
-import logging
import tempfile
-import bcf.data
-from datetime import datetime
from xml.dom import minidom
-from xmlschema import XMLSchema
-from contextlib import contextmanager
-from shutil import copyfile
-cwd = os.path.dirname(os.path.realpath(__file__))
+def load(filepath):
+ filepath = extract_project(filepath)
+ if os.path.isfile(os.path.join(filepath, "bcf.version")):
+ version_path = os.path.join(filepath, "bcf.version")
+ version_id = get_version(version_path)
+ if version_id == "2.1":
+ from bcf.v2.bcfxml import BcfXml
-
-@contextmanager
-def cd(newdir):
- prevdir = os.getcwd()
- os.chdir(os.path.expanduser(newdir))
- try:
- yield
- finally:
- os.chdir(prevdir)
-
-
-class BcfXml:
- def __init__(self):
- self.filepath = None
- self.logger = logging.getLogger("bcfxml")
- self.author = "john@doe.com"
- self.project = bcf.data.Project()
- self.version = "2.1"
- self.topics = {}
-
- def new_project(self):
- self.project.project_id = str(uuid.uuid4())
- self.project.name = "New Project"
- self.topics = {}
- if self.filepath:
- self.close_project()
- self.filepath = tempfile.mkdtemp()
- self.edit_project()
- self.edit_version()
-
- def get_project(self, filepath=None):
- if not filepath:
- return self.project
- zip_file = zipfile.ZipFile(filepath)
- self.filepath = tempfile.mkdtemp()
- zip_file.extractall(self.filepath)
- if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
- data = self._read_xml("project.bcfp", "project.xsd")
- self.project.extension_schema = data["ExtensionSchema"]
- if "Project" in data:
- self.project.project_id = data["Project"]["@ProjectId"]
- self.project.name = data["Project"].get("Name")
- return self.project
-
- def edit_project(self):
- self.document = minidom.Document()
- root = self._create_element(self.document, "ProjectExtension")
- project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
- self._create_element(project, "Name", text=self.project.name)
- self._create_element(root, "ExtensionSchema", text="extensions.xsd")
- with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
- f.write(self.document.toprettyxml(encoding="utf-8"))
-
- def save_project(self, filepath):
- with cd(self.filepath):
- zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
- for root, dirs, files in os.walk("./"):
- for file in files:
- zip_file.write(os.path.join(root, file))
- zip_file.close()
-
- def get_version(self):
- data = self._read_xml("bcf.version", "version.xsd")
- self.version = data["@VersionId"]
- return self.version
-
- def edit_version(self):
- self.document = minidom.Document()
- root = self._create_element(self.document, "Version", {"VersionId": self.version})
- version = self._create_element(root, "DetailedVersion", text=self.version)
- with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
- f.write(self.document.toprettyxml(encoding="utf-8"))
-
- def get_topics(self):
- self.topics = {}
- topics = []
- subdirs = []
- for (dirpath, dirnames, filenames) in os.walk(self.filepath):
- subdirs = dirnames
- break
- for subdir in subdirs:
- self.topics[subdir] = self.get_topic(subdir)
- return self.topics
-
- def get_header(self, guid):
- data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
- if "Header" not in data:
- return
- header = bcf.data.Header()
- for item in data["Header"]["File"]:
- header_file = bcf.data.HeaderFile()
- optional_keys = {
- "filename": "Filename",
- "date": "Date",
- "reference": "Reference",
- "ifc_project": "@IfcProject",
- "ifc_spatial_structure_element": "@IfcSpatialStructureElement",
- "is_external": "@isExternal",
- }
- for key, value in optional_keys.items():
- if value in item:
- setattr(header_file, key, item[value])
- header.files.append(header_file)
- self.topics[guid].header = header
- return header
-
- def get_topic(self, guid):
- if guid in self.topics:
- return self.topics[guid]
- data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
- topic = bcf.data.Topic()
- self.topics[guid] = topic
-
- mandatory_keys = {
- "guid": "@Guid",
- "title": "Title",
- "creation_date": "CreationDate",
- "creation_author": "CreationAuthor",
- }
- for key, value in mandatory_keys.items():
- setattr(topic, key, data["Topic"][value])
-
- optional_keys = {
- "priority": "Priority",
- "index": "Index",
- "labels": "Labels",
- "reference_links": "ReferenceLink",
- "modified_date": "ModifiedDate",
- "modified_author": "ModifiedAuthor",
- "due_date": "DueDate",
- "assigned_to": "AssignedTo",
- "stage": "Stage",
- "description": "Description",
- "topic_status": "@TopicStatus",
- "topic_type": "@TopicType",
- }
- for key, value in optional_keys.items():
- if value in data["Topic"]:
- setattr(topic, key, data["Topic"][value])
-
- if "BimSnippet" in data["Topic"]:
- bim_snippet = bcf.data.BimSnippet()
- keys = {
- "snippet_type": "@SnippetType",
- "is_external": "@IsExternal",
- "reference": "Reference",
- "reference_schema": "ReferenceSchema",
- }
- for key, value in keys.items():
- if value in data["Topic"]["BimSnippet"]:
- setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
- topic.bim_snippet = bim_snippet
-
- if "DocumentReference" in data["Topic"]:
- for item in data["Topic"]["DocumentReference"]:
- document_reference = bcf.data.DocumentReference()
- keys = {
- "referenced_document": "ReferencedDocument",
- "is_external": "@IsExternal",
- "guid": "@Guid",
- "description": "Description",
- }
- for key, value in keys.items():
- if value in item:
- setattr(document_reference, key, item[value])
- topic.document_references.append(document_reference)
-
- if "RelatedTopic" in data["Topic"]:
- for item in data["Topic"]["RelatedTopic"]:
- related_topic = bcf.data.RelatedTopic()
- related_topic.guid = item["@Guid"]
- topic.related_topics.append(related_topic)
- return topic
-
- def add_topic(self, topic=None):
- if topic is None:
- topic = bcf.data.Topic()
- if not topic.guid:
- topic.guid = str(uuid.uuid4())
- if not topic.title:
- topic.title = "New Topic"
- os.mkdir(os.path.join(self.filepath, topic.guid))
- self.edit_topic(topic)
- return topic
-
- def edit_topic(self, topic):
- if not topic.creation_date:
- topic.creation_date = datetime.utcnow().isoformat()
- topic.creation_author = self.author
+ bcfxml = BcfXml()
+ bcfxml.filepath = filepath
+ return bcfxml
else:
- topic.modified_date = datetime.utcnow().isoformat()
- topic.modified_author = self.author
+ from bcf.v3.bcfxml import BcfXml
- self.document = minidom.Document()
- root = self._create_element(self.document, "Markup")
+ bcfxml = BcfXml()
+ bcfxml.filepath = filepath
+ return bcfxml
- self.write_header(topic.header, root)
- topic_el = self._create_element(
- root,
- "Topic",
- {
- "Guid": topic.guid,
- "TopicType": topic.topic_type,
- "TopicStatus": topic.topic_status,
- },
- )
+def get_version(version_path):
+ xmlparse = minidom.parse(version_path)
+ version_el = xmlparse.getElementsByTagName("Version")[0]
+ version = version_el.getAttribute("VersionId")
+ return version
- for reference_link in topic.reference_links:
- self._create_element(topic_el, "ReferenceLink", text=reference_link)
- text_map = {
- "Title": topic.title,
- "Priority": topic.priority,
- "Index": topic.index,
- }
- for key, value in text_map.items():
- if value:
- self._create_element(topic_el, key, text=value)
-
- for label in topic.labels:
- self._create_element(topic_el, "Labels", text=label)
-
- text_map = {
- "CreationDate": topic.creation_date,
- "CreationAuthor": topic.creation_author,
- "ModifiedDate": topic.modified_date,
- "ModifiedAuthor": topic.modified_author,
- "DueDate": topic.due_date,
- "AssignedTo": topic.assigned_to,
- "Stage": topic.stage,
- "Description": topic.description,
- }
- for key, value in text_map.items():
- if value:
- self._create_element(topic_el, key, text=value)
-
- if topic.bim_snippet:
- bim_snippet = self._create_element(
- topic_el,
- "BimSnippet",
- {"SnippetType": topic.bim_snippet.snippet_type, "isExternal": topic.bim_snippet.is_external},
- )
- self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
- self._create_element(bim_snippet, "ReferenceSchema", text=topic.bim_snippet.reference_schema)
- for reference in topic.document_references:
- reference_el = self._create_element(
- topic_el, "DocumentReference", {"Guid": reference.guid, "isExternal": reference.is_external}
- )
- self._create_element(reference_el, "ReferencedDocument", text=reference.referenced_document)
- self._create_element(reference_el, "Description", text=reference.description)
- for related_topic in topic.related_topics:
- self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
-
- self.write_comments(topic.comments, root)
- self.write_viewpoints(topic.viewpoints, root, topic)
-
- with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
- f.write(self.document.toprettyxml(encoding="utf-8"))
-
- def write_header(self, header, root):
- if not header or not header.files:
- return
- header_el = self._create_element(root, "Header")
- for f in header.files:
- file_el = self._create_element(
- header_el,
- "File",
- {
- "IfcProject": f.ifc_project,
- "IfcSpatialStructureElement": f.ifc_spatial_structure_element,
- "isExternal": f.is_external,
- },
- )
- self._create_element(file_el, "Filename", text=f.filename)
- self._create_element(file_el, "Date", text=f.date)
- self._create_element(file_el, "Reference", text=f.reference)
-
- def write_comments(self, comments, root):
- for comment in comments.values():
- comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
- text_map = {
- "Date": comment.date,
- "Author": comment.author,
- "Comment": comment.comment,
- "ModifiedDate": comment.modified_date,
- "ModifiedAuthor": comment.modified_author,
- }
- for key, value in text_map.items():
- if value:
- self._create_element(comment_el, key, text=value)
- if comment.viewpoint:
- self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
-
- def add_comment(self, topic, comment=None):
- if comment is None:
- comment = bcf.data.Comment()
- if not comment.guid:
- comment.guid = str(uuid.uuid4())
- if not comment.comment:
- comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
- topic.comments[comment.guid] = comment
- self.edit_comment(comment, topic)
-
- def edit_comment(self, comment, topic):
- if not comment.date:
- comment.date = datetime.utcnow().isoformat()
- comment.author = self.author
- else:
- comment.modified_date = datetime.utcnow().isoformat()
- comment.modified_author = self.author
- self.edit_topic(topic)
-
- def delete_comment(self, guid, topic):
- if guid in topic.comments:
- del topic.comments[guid]
- self.edit_topic(topic)
-
- def delete_topic(self, guid):
- if guid in self.topics:
- del self.topics[guid]
- shutil.rmtree(os.path.join(self.filepath, guid))
-
- def write_viewpoints(self, viewpoints, root, topic):
- for viewpoint in viewpoints.values():
- viewpoint_el = self._create_element(root, "Viewpoints", {"Guid": viewpoint.guid})
- text_map = {"Viewpoint": viewpoint.viewpoint, "Snapshot": viewpoint.snapshot, "Index": viewpoint.index}
- for key, value in text_map.items():
- if value:
- self._create_element(viewpoint_el, key, text=value)
- self.write_viewpoint(viewpoint, topic)
-
- def write_viewpoint(self, viewpoint, topic):
- document = minidom.Document()
- root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
- self.write_viewpoint_components(viewpoint, root)
- self.write_viewpoint_orthogonal_camera(viewpoint, root)
- self.write_viewpoint_perspective_camera(viewpoint, root)
- self.write_viewpoint_lines(viewpoint, root)
- self.write_viewpoint_clipping_planes(viewpoint, root)
- self.write_viewpoint_bitmaps(viewpoint, root)
- with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
- f.write(document.toprettyxml(encoding="utf-8"))
-
- def write_viewpoint_components(self, viewpoint, parent):
- if not viewpoint.components:
- return
- components_el = self._create_element(parent, "Components")
- if viewpoint.components.view_setup_hints:
- view_setup_hints = self._create_element(
- components_el,
- "ViewSetupHints",
- {
- "SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
- "SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
- "OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
- },
- )
- if viewpoint.components.selection:
- selection_el = self._create_element(components_el, "Selection")
- for selection in viewpoint.components.selection:
- self.write_component(selection, selection_el)
- visibility = self._create_element(
- components_el, "Visibility", {"DefaultVisibility": viewpoint.components.visibility.default_visibility}
- )
- if viewpoint.components.visibility.exceptions:
- exceptions_el = self._create_element(visibility, "Exceptions")
- for exception in viewpoint.components.visibility.exceptions:
- self.write_component(exception, exceptions_el)
- if viewpoint.components.coloring:
- coloring_el = self._create_element(components_el, "Coloring")
- for color in viewpoint.components.coloring:
- color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
- for component in color.components:
- self.write_component(component, color_el)
-
- def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
- if not viewpoint.orthogonal_camera:
- return
- camera = viewpoint.orthogonal_camera
- camera_el = self._create_element(parent, "OrthogonalCamera")
- camera_view_point = self._create_element(camera_el, "CameraViewPoint")
- self.write_vector(camera_view_point, camera.camera_view_point)
- camera_direction = self._create_element(camera_el, "CameraDirection")
- self.write_vector(camera_direction, camera.camera_direction)
- camera_up_vector = self._create_element(camera_el, "CameraUpVector")
- self.write_vector(camera_up_vector, camera.camera_up_vector)
- self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
-
- def write_viewpoint_perspective_camera(self, viewpoint, parent):
- if not viewpoint.perspective_camera:
- return
- camera = viewpoint.perspective_camera
- camera_el = self._create_element(parent, "PerspectiveCamera")
- camera_view_point = self._create_element(camera_el, "CameraViewPoint")
- self.write_vector(camera_view_point, camera.camera_view_point)
- camera_direction = self._create_element(camera_el, "CameraDirection")
- self.write_vector(camera_direction, camera.camera_direction)
- camera_up_vector = self._create_element(camera_el, "CameraUpVector")
- self.write_vector(camera_up_vector, camera.camera_up_vector)
- self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
-
- def write_viewpoint_lines(self, viewpoint, parent):
- if not viewpoint.lines:
- return
- lines_el = self._create_element(parent, "Lines")
- for line in viewpoint.lines:
- line_el = self._create_element(lines_el, "Line")
- start_point_el = self._create_element(line_el, "StartPoint")
- self.write_vector(start_point_el, line.start_point)
- end_point_el = self._create_element(line_el, "EndPoint")
- self.write_vector(end_point_el, line.end_point)
-
- def write_viewpoint_clipping_planes(self, viewpoint, parent):
- if not viewpoint.clipping_planes:
- return
- planes_el = self._create_element(parent, "ClippingPlanes")
- for plane in viewpoint.clipping_planes:
- plane_el = self._create_element(planes_el, "ClippingPlane")
- location_el = self._create_element(plane_el, "Location")
- self.write_vector(location_el, plane.location)
- direction_el = self._create_element(plane_el, "Direction")
- self.write_vector(direction_el, plane.direction)
-
- def write_viewpoint_bitmaps(self, viewpoint, parent):
- if not viewpoint.bitmaps:
- return
- for bitmap in viewpoint.bitmaps:
- bitmap_el = self._create_element(parent, "Bitmap")
-
- text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference}
- for key, value in text_map.items():
- self._create_element(bitmap_el, key, text=value)
-
- location_el = self._create_element(bitmap_el, "Location")
- self.write_vector(location_el, bitmap.location)
- normal_el = self._create_element(bitmap_el, "Normal")
- self.write_vector(normal_el, bitmap.normal)
- up_el = self._create_element(bitmap_el, "Up")
- self.write_vector(up_el, bitmap.up)
-
- self._create_element(bitmap_el, "Height", text=bitmap.height)
-
- def write_vector(self, parent, from_obj):
- self._create_element(parent, "X", text=from_obj.x)
- self._create_element(parent, "Y", text=from_obj.y)
- self._create_element(parent, "Z", text=from_obj.z)
-
- def write_component(self, data, parent):
- component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
- text_map = {"OriginatingSystem": data.originating_system, "AuthoringToolId": data.authoring_tool_id}
- for key, value in text_map.items():
- if value:
- self._create_element(component_el, key, text=value)
-
- def add_viewpoint(self, topic, viewpoint=None):
- if not viewpoint:
- viewpoint = bcf.data.Viewpoint()
- if not viewpoint.guid:
- viewpoint.guid = str(uuid.uuid4())
- if not viewpoint.viewpoint:
- viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
- if viewpoint.snapshot:
- topic_filepath = os.path.join(self.filepath, topic.guid)
- filepath = os.path.join(topic_filepath, viewpoint.snapshot)
- if not os.path.exists(filepath):
- filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
- copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
- viewpoint.snapshot = filename
- topic.viewpoints[viewpoint.guid] = viewpoint
- self.edit_topic(topic)
-
- def delete_viewpoint(self, guid, topic):
- if guid not in topic.viewpoints:
- return
- viewpoint = topic.viewpoints[guid]
- if viewpoint.snapshot:
- filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
- if os.path.exists(filepath):
- os.remove(filepath)
- if viewpoint.viewpoint:
- filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
- if os.path.exists(filepath):
- os.remove(filepath)
- for bitmap in viewpoint.bitmaps:
- if not bitmap.reference:
- continue
- filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
- if os.path.exists(filepath):
- os.remove(filepath)
- del topic.viewpoints[guid]
- self.edit_topic(topic)
-
- def delete_file(self, topic, index):
- if not topic.header:
- return
- f = topic.header.files.pop(index)
- filepath = os.path.join(self.filepath, topic.guid, f.reference)
- if not f.is_external and os.path.exists(filepath):
- os.remove(filepath)
- self.edit_topic(topic)
-
- def delete_bim_snippet(self, topic):
- if not topic.bim_snippet:
- return
- if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
- filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
- if os.path.exists(filepath):
- os.remove(filepath)
- topic.bim_snippet = None
- self.edit_topic(topic)
-
- def delete_document_reference(self, topic, index):
- document_reference = topic.document_references[index]
- if document_reference.referenced_document and not document_reference.is_external:
- filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
- if os.path.exists(filepath):
- os.remove(filepath)
- del topic.document_references[index]
- self.edit_topic(topic)
-
- def add_document_reference(self, topic, document_reference):
- if os.path.exists(document_reference.referenced_document):
- topic_filepath = os.path.join(self.filepath, topic.guid)
- filename = os.path.basename(document_reference.referenced_document)
- copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename))
- document_reference.referenced_document = filename
- document_reference.is_external = False
- else:
- document_reference.is_external = True
- if not document_reference.guid:
- document_reference.guid = str(uuid.uuid4())
- topic.document_references.append(document_reference)
- self.edit_topic(topic)
-
- def add_bim_snippet(self, topic, bim_snippet):
- if topic.bim_snippet:
- self.delete_bim_snippet(topic)
- if os.path.exists(bim_snippet.reference):
- topic_filepath = os.path.join(self.filepath, topic.guid)
- filename = os.path.basename(bim_snippet.reference)
- copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
- bim_snippet.reference = filename
- bim_snippet.is_external = False
- else:
- bim_snippet.is_external = True
- topic.bim_snippet = bim_snippet
- self.edit_topic(topic)
-
- def add_file(self, topic, header_file):
- if os.path.exists(header_file.reference):
- topic_filepath = os.path.join(self.filepath, topic.guid)
- header_file.filename = os.path.basename(header_file.reference)
- copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
- header_file.reference = header_file.filename
- header_file.is_external = False
- header_file.date = datetime.utcnow().isoformat()
- if not topic.header:
- topic.header = bcf.data.Header()
- topic.header.files.append(header_file)
- self.edit_topic(topic)
-
- def get_comments(self, guid):
- comments = {}
- data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
- if "Comment" not in data:
- return comments
- for item in data["Comment"]:
- comment = bcf.data.Comment()
- mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
- for key, value in mandatory_keys.items():
- setattr(comment, key, item[value])
- optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
- for key, value in optional_keys.items():
- if value in item:
- setattr(comment, key, item[value])
- if "Viewpoint" in item:
- viewpoint = bcf.data.Viewpoint()
- viewpoint.guid = item["Viewpoint"]["@Guid"]
- comment.viewpoint = viewpoint
- comments[comment.guid] = comment
- self.topics[guid].comments = comments
- return comments
-
- def get_viewpoints(self, guid):
- viewpoints = {}
- data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
- if "Viewpoints" not in data:
- return viewpoints
- for item in data["Viewpoints"]:
- viewpoint = self.get_viewpoint(item, guid)
- viewpoints[viewpoint.guid] = viewpoint
- self.topics[guid].viewpoints = viewpoints
- return viewpoints
-
- def get_viewpoint(self, data, topic_guid):
- viewpoint = bcf.data.Viewpoint()
- viewpoint.guid = data["@Guid"]
- optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
- for key, value in optional_keys.items():
- if value in data:
- setattr(viewpoint, key, data[value])
- visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
- viewpoint.components = self.get_viewpoint_components(visinfo)
- viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
- viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
- viewpoint.lines = self.get_viewpoint_lines(visinfo)
- viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
- viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
- return viewpoint
-
- def get_viewpoint_components(self, visinfo):
- if "Components" not in visinfo:
- return None
- components = bcf.data.Components()
- data = visinfo["Components"]
- if "ViewSetupHints" in data:
- view_setup_hints = bcf.data.ViewSetupHints()
- optional_keys = {
- "spaces_visible": "@SpacesVisible",
- "space_boundaries_visible": "@SpaceBoundariesVisible",
- "openings_visible": "@OpeningsVisible",
- }
- for key, value in optional_keys.items():
- if value in data["ViewSetupHints"]:
- setattr(view_setup_hints, key, data["ViewSetupHints"][value])
- components.view_setup_hints = view_setup_hints
- if "Selection" in data and "Component" in data["Selection"]:
- for item in data["Selection"]["Component"]:
- components.selection.append(self.get_component(item))
- if "Visibility" in data:
- component_visibility = bcf.data.ComponentVisibility()
- if "@DefaultVisibility" in data["Visibility"]:
- component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
- if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
- for item in data["Visibility"]["Exceptions"]["Component"]:
- component_visibility.exceptions.append(self.get_component(item))
- components.visibility = component_visibility
- if "Coloring" in data and "Color" in data["Coloring"]:
- for item in data["Coloring"]["Color"]:
- color = bcf.data.Color()
- color.color = item["@Color"]
- for item2 in item["Component"]:
- color.components.append(self.get_component(item2))
- components.coloring.append(color)
- return components
-
- def get_viewpoint_orthogonal_camera(self, visinfo):
- if "OrthogonalCamera" not in visinfo:
- return None
- camera = bcf.data.OrthogonalCamera()
- data = visinfo["OrthogonalCamera"]
- self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
- self.set_vector(camera.camera_direction, data["CameraDirection"])
- self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
- camera.view_to_world_scale = data["ViewToWorldScale"]
- return camera
-
- def get_viewpoint_perspective_camera(self, visinfo):
- if "PerspectiveCamera" not in visinfo:
- return None
- camera = bcf.data.PerspectiveCamera()
- data = visinfo["PerspectiveCamera"]
- self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
- self.set_vector(camera.camera_direction, data["CameraDirection"])
- self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
- camera.field_of_view = data["FieldOfView"]
- return camera
-
- def get_viewpoint_lines(self, visinfo):
- if "Lines" not in visinfo:
- return []
- lines = []
- for item in visinfo["Lines"]["Line"]:
- line = bcf.data.Line()
- self.set_vector(line.start_point, item["StartPoint"])
- self.set_vector(line.end_point, item["EndPoint"])
- lines.append(line)
- return lines
-
- def get_viewpoint_clipping_planes(self, visinfo):
- if "ClippingPlanes" not in visinfo:
- return []
- planes = []
- for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
- plane = bcf.data.ClippingPlane()
- self.set_vector(plane.location, item["Location"])
- self.set_vector(plane.direction, item["Direction"])
- planes.append(plane)
- return planes
-
- def get_viewpoint_bitmaps(self, visinfo):
- if "Bitmap" not in visinfo:
- return []
- bitmaps = []
- for item in visinfo["Bitmap"]:
- bitmap = bcf.data.Bitmap()
- bitmap.reference = item["Reference"]
- bitmap.bitmap_type = item["Bitmap"].upper()
- self.set_vector(bitmap.location, item["Location"])
- self.set_vector(bitmap.normal, item["Normal"])
- self.set_vector(bitmap.up, item["Up"])
- bitmap.height = item["Height"]
- bitmaps.append(bitmap)
- return bitmaps
-
- def set_vector(self, to_obj, from_xml):
- to_obj.x = from_xml["X"]
- to_obj.y = from_xml["Y"]
- to_obj.z = from_xml["Z"]
-
- def get_component(self, data):
- component = bcf.data.Component()
- optional_keys = {
- "originating_system": "OriginatingSystem",
- "authoring_tool_id": "AuthoringToolId",
- "ifc_guid": "@IfcGuid",
- }
- for key, value in optional_keys.items():
- if value in data:
- setattr(component, key, data[value])
- return component
-
- def close_project(self):
- shutil.rmtree(self.filepath)
-
- def _read_xml(self, filename, xsd):
- schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
- filepath = os.path.join(self.filepath, filename)
- (data, errors) = schema.to_dict(filepath, validation="lax")
- for error in errors:
- self.logger.error(error)
- return data
-
- def _create_element(self, parent, name, attributes={}, text=None):
- element = self.document.createElement(name)
- for key, value in attributes.items():
- if isinstance(value, bool):
- element.setAttribute(key, str(value).lower())
- elif value:
- element.setAttribute(key, value)
- if text is not None:
- text = self.document.createTextNode(str(text))
- element.appendChild(text)
- parent.appendChild(element)
- return element
-
- def __del__(self):
- self.close_project()
+def extract_project(filepath):
+ if not filepath:
+ return
+ zip_file = zipfile.ZipFile(filepath)
+ filepath = tempfile.mkdtemp()
+ zip_file.extractall(filepath)
+ return filepath
diff --git a/src/bcf/bcf/v2/bcfxml.py b/src/bcf/bcf/v2/bcfxml.py
new file mode 100644
index 0000000000..3b4dd722b0
--- /dev/null
+++ b/src/bcf/bcf/v2/bcfxml.py
@@ -0,0 +1,771 @@
+import os
+import uuid
+import shutil
+import zipfile
+import logging
+import tempfile
+import bcf.v2.data
+from datetime import datetime
+from xml.dom import minidom
+from xmlschema import XMLSchema
+from contextlib import contextmanager
+from shutil import copyfile
+
+
+cwd = os.path.dirname(os.path.realpath(__file__))
+
+
+@contextmanager
+def cd(newdir):
+ prevdir = os.getcwd()
+ os.chdir(os.path.expanduser(newdir))
+ try:
+ yield
+ finally:
+ os.chdir(prevdir)
+
+
+class BcfXml:
+ def __init__(self):
+ self.filepath = None
+ self.logger = logging.getLogger("bcfxml")
+ self.author = "john@doe.com"
+ self.project = bcf.v2.data.Project()
+ self.version = "2.1"
+ self.topics = {}
+
+ def new_project(self):
+ self.project.project_id = str(uuid.uuid4())
+ self.project.name = "New Project"
+ self.topics = {}
+ if self.filepath:
+ self.close_project()
+ self.filepath = tempfile.mkdtemp()
+ self.edit_project()
+ self.edit_version()
+
+ def get_project(self, filepath=None):
+ if not filepath:
+ return self.project
+ if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
+ data = self._read_xml("project.bcfp", "project.xsd")
+ self.project.extension_schema = data["ExtensionSchema"]
+ if "Project" in data:
+ self.project.project_id = data["Project"]["@ProjectId"]
+ self.project.name = data["Project"].get("Name")
+ return self.project
+
+ def edit_project(self):
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "ProjectExtension")
+ project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
+ self._create_element(project, "Name", text=self.project.name)
+ self._create_element(root, "ExtensionSchema", text="extensions.xsd")
+ with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def save_project(self, filepath):
+ with cd(self.filepath):
+ zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
+ for root, dirs, files in os.walk("./"):
+ for file in files:
+ zip_file.write(os.path.join(root, file))
+ zip_file.close()
+
+ def get_version(self):
+ data = self._read_xml("bcf.version", "version.xsd")
+ self.version = data["@VersionId"]
+ return self.version
+
+ def edit_version(self):
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "Version", {"VersionId": self.version})
+ version = self._create_element(root, "DetailedVersion", text=self.version)
+ with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def get_topics(self):
+ self.topics = {}
+ topics = []
+ subdirs = []
+ for (dirpath, dirnames, filenames) in os.walk(self.filepath):
+ subdirs = dirnames
+ break
+ for subdir in subdirs:
+ try:
+ uuid.UUID(subdir)
+ except ValueError:
+ continue
+ if not os.path.exists(os.path.join(self.filepath, subdir, "markup.bcf")):
+ continue
+ self.topics[subdir] = self.get_topic(subdir)
+ return self.topics
+
+ def get_header(self, guid):
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ if "Header" not in data:
+ return
+ header = bcf.v2.data.Header()
+ for item in data["Header"]["File"]:
+ header_file = bcf.v2.data.HeaderFile()
+ optional_keys = {
+ "filename": "Filename",
+ "date": "Date",
+ "reference": "Reference",
+ "ifc_project": "@IfcProject",
+ "ifc_spatial_structure_element": "@IfcSpatialStructureElement",
+ "is_external": "@isExternal",
+ }
+ for key, value in optional_keys.items():
+ if value in item:
+ setattr(header_file, key, item[value])
+ header.files.append(header_file)
+ self.topics[guid].header = header
+ return header
+
+ def get_topic(self, guid):
+ if guid in self.topics:
+ return self.topics[guid]
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ topic = bcf.v2.data.Topic()
+ self.topics[guid] = topic
+
+ mandatory_keys = {
+ "guid": "@Guid",
+ "title": "Title",
+ "creation_date": "CreationDate",
+ "creation_author": "CreationAuthor",
+ }
+ for key, value in mandatory_keys.items():
+ setattr(topic, key, data["Topic"][value])
+
+ optional_keys = {
+ "priority": "Priority",
+ "index": "Index",
+ "labels": "Labels",
+ "reference_links": "ReferenceLink",
+ "modified_date": "ModifiedDate",
+ "modified_author": "ModifiedAuthor",
+ "due_date": "DueDate",
+ "assigned_to": "AssignedTo",
+ "stage": "Stage",
+ "description": "Description",
+ "topic_status": "@TopicStatus",
+ "topic_type": "@TopicType",
+ }
+ for key, value in optional_keys.items():
+ if value in data["Topic"]:
+ setattr(topic, key, data["Topic"][value])
+
+ if "BimSnippet" in data["Topic"]:
+ bim_snippet = bcf.v2.data.BimSnippet()
+ keys = {
+ "snippet_type": "@SnippetType",
+ "is_external": "@IsExternal",
+ "reference": "Reference",
+ "reference_schema": "ReferenceSchema",
+ }
+ for key, value in keys.items():
+ if value in data["Topic"]["BimSnippet"]:
+ setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
+ topic.bim_snippet = bim_snippet
+
+ if "DocumentReference" in data["Topic"]:
+ for item in data["Topic"]["DocumentReference"]:
+ document_reference = bcf.v2.data.DocumentReference()
+ keys = {
+ "referenced_document": "ReferencedDocument",
+ "is_external": "@IsExternal",
+ "guid": "@Guid",
+ "description": "Description",
+ }
+ for key, value in keys.items():
+ if value in item:
+ setattr(document_reference, key, item[value])
+ topic.document_references.append(document_reference)
+
+ if "RelatedTopic" in data["Topic"]:
+ for item in data["Topic"]["RelatedTopic"]:
+ related_topic = bcf.v2.data.RelatedTopic()
+ related_topic.guid = item["@Guid"]
+ topic.related_topics.append(related_topic)
+ return topic
+
+ def add_topic(self, topic=None):
+ if topic is None:
+ topic = bcf.v2.data.Topic()
+ if not topic.guid:
+ topic.guid = str(uuid.uuid4())
+ if not topic.title:
+ topic.title = "New Topic"
+ os.mkdir(os.path.join(self.filepath, topic.guid))
+ self.edit_topic(topic)
+ return topic
+
+ def edit_topic(self, topic):
+ if not topic.creation_date:
+ topic.creation_date = datetime.utcnow().isoformat()
+ topic.creation_author = self.author
+ else:
+ topic.modified_date = datetime.utcnow().isoformat()
+ topic.modified_author = self.author
+
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "Markup")
+
+ self.write_header(topic.header, root)
+
+ topic_el = self._create_element(
+ root,
+ "Topic",
+ {
+ "Guid": topic.guid,
+ "TopicType": topic.topic_type,
+ "TopicStatus": topic.topic_status,
+ },
+ )
+
+ for reference_link in topic.reference_links:
+ self._create_element(topic_el, "ReferenceLink", text=reference_link)
+
+ text_map = {
+ "Title": topic.title,
+ "Priority": topic.priority,
+ "Index": topic.index,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(topic_el, key, text=value)
+
+ for label in topic.labels:
+ self._create_element(topic_el, "Labels", text=label)
+
+ text_map = {
+ "CreationDate": topic.creation_date,
+ "CreationAuthor": topic.creation_author,
+ "ModifiedDate": topic.modified_date,
+ "ModifiedAuthor": topic.modified_author,
+ "DueDate": topic.due_date,
+ "AssignedTo": topic.assigned_to,
+ "Stage": topic.stage,
+ "Description": topic.description,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(topic_el, key, text=value)
+
+ if topic.bim_snippet:
+ bim_snippet = self._create_element(
+ topic_el,
+ "BimSnippet",
+ {"SnippetType": topic.bim_snippet.snippet_type, "isExternal": topic.bim_snippet.is_external},
+ )
+ self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
+ self._create_element(bim_snippet, "ReferenceSchema", text=topic.bim_snippet.reference_schema)
+ for reference in topic.document_references:
+ reference_el = self._create_element(
+ topic_el, "DocumentReference", {"Guid": reference.guid, "isExternal": reference.is_external}
+ )
+ self._create_element(reference_el, "ReferencedDocument", text=reference.referenced_document)
+ self._create_element(reference_el, "Description", text=reference.description)
+ for related_topic in topic.related_topics:
+ self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
+
+ self.write_comments(topic.comments, root)
+ self.write_viewpoints(topic.viewpoints, root, topic)
+
+ with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def write_header(self, header, root):
+ if not header or not header.files:
+ return
+ header_el = self._create_element(root, "Header")
+ for f in header.files:
+ file_el = self._create_element(
+ header_el,
+ "File",
+ {
+ "IfcProject": f.ifc_project,
+ "IfcSpatialStructureElement": f.ifc_spatial_structure_element,
+ "isExternal": f.is_external,
+ },
+ )
+ if f.filename:
+ self._create_element(file_el, "Filename", text=f.filename)
+ if f.date:
+ self._create_element(file_el, "Date", text=f.date)
+ if f.reference:
+ self._create_element(file_el, "Reference", text=f.reference)
+
+ def write_comments(self, comments, root):
+ for comment in comments.values():
+ comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
+ text_map = {
+ "Date": comment.date,
+ "Author": comment.author,
+ "Comment": comment.comment,
+ "ModifiedDate": comment.modified_date,
+ "ModifiedAuthor": comment.modified_author,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(comment_el, key, text=value)
+ if comment.viewpoint:
+ self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
+
+ def add_comment(self, topic, comment=None):
+ if comment is None:
+ comment = bcf.v2.data.Comment()
+ if not comment.guid:
+ comment.guid = str(uuid.uuid4())
+ if not comment.comment:
+ comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
+ topic.comments[comment.guid] = comment
+ self.edit_comment(comment, topic)
+
+ def edit_comment(self, comment, topic):
+ if not comment.date:
+ comment.date = datetime.utcnow().isoformat()
+ comment.author = self.author
+ else:
+ comment.modified_date = datetime.utcnow().isoformat()
+ comment.modified_author = self.author
+ self.edit_topic(topic)
+
+ def delete_comment(self, guid, topic):
+ if guid in topic.comments:
+ del topic.comments[guid]
+ self.edit_topic(topic)
+
+ def delete_topic(self, guid):
+ if guid in self.topics:
+ del self.topics[guid]
+ shutil.rmtree(os.path.join(self.filepath, guid))
+
+ def write_viewpoints(self, viewpoints, root, topic):
+ for viewpoint in viewpoints.values():
+ viewpoint_el = self._create_element(root, "Viewpoints", {"Guid": viewpoint.guid})
+ text_map = {"Viewpoint": viewpoint.viewpoint, "Snapshot": viewpoint.snapshot, "Index": viewpoint.index}
+ for key, value in text_map.items():
+ if value:
+ self._create_element(viewpoint_el, key, text=value)
+ self.write_viewpoint(viewpoint, topic)
+
+ def write_viewpoint(self, viewpoint, topic):
+ document = minidom.Document()
+ root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
+ self.write_viewpoint_components(viewpoint, root)
+ self.write_viewpoint_orthogonal_camera(viewpoint, root)
+ self.write_viewpoint_perspective_camera(viewpoint, root)
+ self.write_viewpoint_lines(viewpoint, root)
+ self.write_viewpoint_clipping_planes(viewpoint, root)
+ self.write_viewpoint_bitmaps(viewpoint, root)
+ with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
+ f.write(document.toprettyxml(encoding="utf-8"))
+
+ def write_viewpoint_components(self, viewpoint, parent):
+ if not viewpoint.components:
+ return
+ components_el = self._create_element(parent, "Components")
+ if viewpoint.components.view_setup_hints:
+ view_setup_hints = self._create_element(
+ components_el,
+ "ViewSetupHints",
+ {
+ "SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
+ "SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
+ "OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
+ },
+ )
+ if viewpoint.components.selection:
+ selection_el = self._create_element(components_el, "Selection")
+ for selection in viewpoint.components.selection:
+ self.write_component(selection, selection_el)
+ visibility = self._create_element(
+ components_el, "Visibility", {"DefaultVisibility": viewpoint.components.visibility.default_visibility}
+ )
+ if viewpoint.components.visibility.exceptions:
+ exceptions_el = self._create_element(visibility, "Exceptions")
+ for exception in viewpoint.components.visibility.exceptions:
+ self.write_component(exception, exceptions_el)
+ if viewpoint.components.coloring:
+ coloring_el = self._create_element(components_el, "Coloring")
+ for color in viewpoint.components.coloring:
+ color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
+ for component in color.components:
+ self.write_component(component, color_el)
+
+ def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
+ if not viewpoint.orthogonal_camera:
+ return
+ camera = viewpoint.orthogonal_camera
+ camera_el = self._create_element(parent, "OrthogonalCamera")
+ camera_view_point = self._create_element(camera_el, "CameraViewPoint")
+ self.write_vector(camera_view_point, camera.camera_view_point)
+ camera_direction = self._create_element(camera_el, "CameraDirection")
+ self.write_vector(camera_direction, camera.camera_direction)
+ camera_up_vector = self._create_element(camera_el, "CameraUpVector")
+ self.write_vector(camera_up_vector, camera.camera_up_vector)
+ self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
+
+ def write_viewpoint_perspective_camera(self, viewpoint, parent):
+ if not viewpoint.perspective_camera:
+ return
+ camera = viewpoint.perspective_camera
+ camera_el = self._create_element(parent, "PerspectiveCamera")
+ camera_view_point = self._create_element(camera_el, "CameraViewPoint")
+ self.write_vector(camera_view_point, camera.camera_view_point)
+ camera_direction = self._create_element(camera_el, "CameraDirection")
+ self.write_vector(camera_direction, camera.camera_direction)
+ camera_up_vector = self._create_element(camera_el, "CameraUpVector")
+ self.write_vector(camera_up_vector, camera.camera_up_vector)
+ self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
+
+ def write_viewpoint_lines(self, viewpoint, parent):
+ if not viewpoint.lines:
+ return
+ lines_el = self._create_element(parent, "Lines")
+ for line in viewpoint.lines:
+ line_el = self._create_element(lines_el, "Line")
+ start_point_el = self._create_element(line_el, "StartPoint")
+ self.write_vector(start_point_el, line.start_point)
+ end_point_el = self._create_element(line_el, "EndPoint")
+ self.write_vector(end_point_el, line.end_point)
+
+ def write_viewpoint_clipping_planes(self, viewpoint, parent):
+ if not viewpoint.clipping_planes:
+ return
+ planes_el = self._create_element(parent, "ClippingPlanes")
+ for plane in viewpoint.clipping_planes:
+ plane_el = self._create_element(planes_el, "ClippingPlane")
+ location_el = self._create_element(plane_el, "Location")
+ self.write_vector(location_el, plane.location)
+ direction_el = self._create_element(plane_el, "Direction")
+ self.write_vector(direction_el, plane.direction)
+
+ def write_viewpoint_bitmaps(self, viewpoint, parent):
+ if not viewpoint.bitmaps:
+ return
+ for bitmap in viewpoint.bitmaps:
+ bitmap_el = self._create_element(parent, "Bitmap")
+
+ text_map = {"Bitmap": bitmap.bitmap_format, "Reference": bitmap.reference}
+ for key, value in text_map.items():
+ self._create_element(bitmap_el, key, text=value)
+
+ location_el = self._create_element(bitmap_el, "Location")
+ self.write_vector(location_el, bitmap.location)
+ normal_el = self._create_element(bitmap_el, "Normal")
+ self.write_vector(normal_el, bitmap.normal)
+ up_el = self._create_element(bitmap_el, "Up")
+ self.write_vector(up_el, bitmap.up)
+
+ self._create_element(bitmap_el, "Height", text=bitmap.height)
+
+ def write_vector(self, parent, from_obj):
+ self._create_element(parent, "X", text=from_obj.x)
+ self._create_element(parent, "Y", text=from_obj.y)
+ self._create_element(parent, "Z", text=from_obj.z)
+
+ def write_component(self, data, parent):
+ component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
+ text_map = {"OriginatingSystem": data.originating_system, "AuthoringToolId": data.authoring_tool_id}
+ for key, value in text_map.items():
+ if value:
+ self._create_element(component_el, key, text=value)
+
+ def add_viewpoint(self, topic, viewpoint=None):
+ if not viewpoint:
+ viewpoint = bcf.v2.data.Viewpoint()
+ if not viewpoint.guid:
+ viewpoint.guid = str(uuid.uuid4())
+ if not viewpoint.viewpoint:
+ viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
+ if viewpoint.snapshot:
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filepath = os.path.join(topic_filepath, viewpoint.snapshot)
+ if not os.path.exists(filepath):
+ filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
+ copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
+ viewpoint.snapshot = filename
+ topic.viewpoints[viewpoint.guid] = viewpoint
+ self.edit_topic(topic)
+
+ def delete_viewpoint(self, guid, topic):
+ if guid not in topic.viewpoints:
+ return
+ viewpoint = topic.viewpoints[guid]
+ if viewpoint.snapshot:
+ filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ if viewpoint.viewpoint:
+ filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ for bitmap in viewpoint.bitmaps:
+ if not bitmap.reference:
+ continue
+ filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ del topic.viewpoints[guid]
+ self.edit_topic(topic)
+
+ def delete_file(self, topic, index):
+ if not topic.header:
+ return
+ f = topic.header.files.pop(index)
+ filepath = os.path.join(self.filepath, topic.guid, f.reference)
+ if not f.is_external and os.path.exists(filepath):
+ os.remove(filepath)
+ self.edit_topic(topic)
+
+ def delete_bim_snippet(self, topic):
+ if not topic.bim_snippet:
+ return
+ if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
+ filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ topic.bim_snippet = None
+ self.edit_topic(topic)
+
+ def delete_document_reference(self, topic, index):
+ document_reference = topic.document_references[index]
+ if document_reference.referenced_document and not document_reference.is_external:
+ filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ del topic.document_references[index]
+ self.edit_topic(topic)
+
+ def add_document_reference(self, topic, document_reference):
+ if os.path.exists(document_reference.referenced_document):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filename = os.path.basename(document_reference.referenced_document)
+ copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename))
+ document_reference.referenced_document = filename
+ document_reference.is_external = False
+ else:
+ document_reference.is_external = True
+ if not document_reference.guid:
+ document_reference.guid = str(uuid.uuid4())
+ topic.document_references.append(document_reference)
+ self.edit_topic(topic)
+
+ def add_bim_snippet(self, topic, bim_snippet):
+ if topic.bim_snippet:
+ self.delete_bim_snippet(topic)
+ if os.path.exists(bim_snippet.reference):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filename = os.path.basename(bim_snippet.reference)
+ copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
+ bim_snippet.reference = filename
+ bim_snippet.is_external = False
+ else:
+ bim_snippet.is_external = True
+ topic.bim_snippet = bim_snippet
+ self.edit_topic(topic)
+
+ def add_file(self, topic, header_file):
+ if os.path.exists(header_file.reference):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ header_file.filename = os.path.basename(header_file.reference)
+ copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
+ header_file.reference = header_file.filename
+ header_file.is_external = False
+ header_file.date = datetime.utcnow().isoformat()
+ if not topic.header:
+ topic.header = bcf.v2.data.Header()
+ topic.header.files.append(header_file)
+ self.edit_topic(topic)
+
+ def get_comments(self, guid):
+ comments = {}
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ if "Comment" not in data:
+ return comments
+ for item in data["Comment"]:
+ comment = bcf.v2.data.Comment()
+ mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
+ for key, value in mandatory_keys.items():
+ setattr(comment, key, item[value])
+ optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
+ for key, value in optional_keys.items():
+ if value in item:
+ setattr(comment, key, item[value])
+ if "Viewpoint" in item:
+ viewpoint = bcf.v2.data.Viewpoint()
+ viewpoint.guid = item["Viewpoint"]["@Guid"]
+ comment.viewpoint = viewpoint
+ comments[comment.guid] = comment
+ self.topics[guid].comments = comments
+ return comments
+
+ def get_viewpoints(self, guid):
+ viewpoints = {}
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ if "Viewpoints" not in data:
+ return viewpoints
+ for item in data["Viewpoints"]:
+ viewpoint = self.get_viewpoint(item, guid)
+ viewpoints[viewpoint.guid] = viewpoint
+ self.topics[guid].viewpoints = viewpoints
+ return viewpoints
+
+ def get_viewpoint(self, data, topic_guid):
+ viewpoint = bcf.v2.data.Viewpoint()
+ viewpoint.guid = data["@Guid"]
+ optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
+ for key, value in optional_keys.items():
+ if value in data:
+ setattr(viewpoint, key, data[value])
+ visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
+ viewpoint.components = self.get_viewpoint_components(visinfo)
+ viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
+ viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
+ viewpoint.lines = self.get_viewpoint_lines(visinfo)
+ viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
+ viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
+ return viewpoint
+
+ def get_viewpoint_components(self, visinfo):
+ if "Components" not in visinfo:
+ return None
+ components = bcf.v2.data.Components()
+ data = visinfo["Components"]
+ if "ViewSetupHints" in data:
+ view_setup_hints = bcf.v2.data.ViewSetupHints()
+ optional_keys = {
+ "spaces_visible": "@SpacesVisible",
+ "space_boundaries_visible": "@SpaceBoundariesVisible",
+ "openings_visible": "@OpeningsVisible",
+ }
+ for key, value in optional_keys.items():
+ if value in data["ViewSetupHints"]:
+ setattr(view_setup_hints, key, data["ViewSetupHints"][value])
+ components.view_setup_hints = view_setup_hints
+ if "Selection" in data and "Component" in data["Selection"]:
+ for item in data["Selection"]["Component"]:
+ components.selection.append(self.get_component(item))
+ if "Visibility" in data:
+ component_visibility = bcf.v2.data.ComponentVisibility()
+ if "@DefaultVisibility" in data["Visibility"]:
+ component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
+ if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
+ for item in data["Visibility"]["Exceptions"]["Component"]:
+ component_visibility.exceptions.append(self.get_component(item))
+ components.visibility = component_visibility
+ if "Coloring" in data and "Color" in data["Coloring"]:
+ for item in data["Coloring"]["Color"]:
+ color = bcf.v2.data.Color()
+ color.color = item["@Color"]
+ for item2 in item["Component"]:
+ color.components.append(self.get_component(item2))
+ components.coloring.append(color)
+ return components
+
+ def get_viewpoint_orthogonal_camera(self, visinfo):
+ if "OrthogonalCamera" not in visinfo:
+ return None
+ camera = bcf.v2.data.OrthogonalCamera()
+ data = visinfo["OrthogonalCamera"]
+ self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
+ self.set_vector(camera.camera_direction, data["CameraDirection"])
+ self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
+ camera.view_to_world_scale = data["ViewToWorldScale"]
+ return camera
+
+ def get_viewpoint_perspective_camera(self, visinfo):
+ if "PerspectiveCamera" not in visinfo:
+ return None
+ camera = bcf.v2.data.PerspectiveCamera()
+ data = visinfo["PerspectiveCamera"]
+ self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
+ self.set_vector(camera.camera_direction, data["CameraDirection"])
+ self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
+ camera.field_of_view = data["FieldOfView"]
+ return camera
+
+ def get_viewpoint_lines(self, visinfo):
+ if "Lines" not in visinfo:
+ return []
+ lines = []
+ for item in visinfo["Lines"]["Line"]:
+ line = bcf.v2.data.Line()
+ self.set_vector(line.start_point, item["StartPoint"])
+ self.set_vector(line.end_point, item["EndPoint"])
+ lines.append(line)
+ return lines
+
+ def get_viewpoint_clipping_planes(self, visinfo):
+ if "ClippingPlanes" not in visinfo:
+ return []
+ planes = []
+ for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
+ plane = bcf.v2.data.ClippingPlane()
+ self.set_vector(plane.location, item["Location"])
+ self.set_vector(plane.direction, item["Direction"])
+ planes.append(plane)
+ return planes
+
+ def get_viewpoint_bitmaps(self, visinfo):
+ if "Bitmap" not in visinfo:
+ return []
+ bitmaps = []
+ for item in visinfo["Bitmap"]:
+ bitmap = bcf.v2.data.Bitmap()
+ bitmap.reference = item["Reference"]
+ bitmap.bitmap_format = item["Bitmap"].upper()
+ self.set_vector(bitmap.location, item["Location"])
+ self.set_vector(bitmap.normal, item["Normal"])
+ self.set_vector(bitmap.up, item["Up"])
+ bitmap.height = item["Height"]
+ bitmaps.append(bitmap)
+ return bitmaps
+
+ def set_vector(self, to_obj, from_xml):
+ to_obj.x = from_xml["X"]
+ to_obj.y = from_xml["Y"]
+ to_obj.z = from_xml["Z"]
+
+ def get_component(self, data):
+ component = bcf.v2.data.Component()
+ optional_keys = {
+ "originating_system": "OriginatingSystem",
+ "authoring_tool_id": "AuthoringToolId",
+ "ifc_guid": "@IfcGuid",
+ }
+ for key, value in optional_keys.items():
+ if value in data:
+ setattr(component, key, data[value])
+ return component
+
+ def close_project(self):
+ shutil.rmtree(self.filepath)
+
+ def _read_xml(self, filename, xsd):
+ schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
+ filepath = os.path.join(self.filepath, filename)
+ (data, errors) = schema.to_dict(filepath, validation="lax")
+ for error in errors:
+ self.logger.error(error)
+ return data
+
+ def _create_element(self, parent, name, attributes={}, text=None):
+ element = self.document.createElement(name)
+ for key, value in attributes.items():
+ if isinstance(value, bool):
+ element.setAttribute(key, str(value).lower())
+ elif value:
+ element.setAttribute(key, value)
+ if text is not None:
+ text = self.document.createTextNode(str(text))
+ element.appendChild(text)
+ parent.appendChild(element)
+ return element
+
+ def __del__(self):
+ self.close_project()
diff --git a/src/bcf/bcf/data.py b/src/bcf/bcf/v2/data.py
similarity index 98%
rename from src/bcf/bcf/data.py
rename to src/bcf/bcf/v2/data.py
index ce4431da9a..534aacaffc 100644
--- a/src/bcf/bcf/data.py
+++ b/src/bcf/bcf/v2/data.py
@@ -157,7 +157,7 @@ class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
- self.bitmap_type = "PNG" # Enum of png or jpg
+ self.bitmap_format = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
diff --git a/src/bcf/bcf/xsd/markup.xsd b/src/bcf/bcf/v2/xsd/markup.xsd
similarity index 100%
rename from src/bcf/bcf/xsd/markup.xsd
rename to src/bcf/bcf/v2/xsd/markup.xsd
diff --git a/src/bcf/bcf/xsd/project.xsd b/src/bcf/bcf/v2/xsd/project.xsd
similarity index 100%
rename from src/bcf/bcf/xsd/project.xsd
rename to src/bcf/bcf/v2/xsd/project.xsd
diff --git a/src/bcf/bcf/xsd/version.xsd b/src/bcf/bcf/v2/xsd/version.xsd
similarity index 100%
rename from src/bcf/bcf/xsd/version.xsd
rename to src/bcf/bcf/v2/xsd/version.xsd
diff --git a/src/bcf/bcf/xsd/visinfo.xsd b/src/bcf/bcf/v2/xsd/visinfo.xsd
similarity index 100%
rename from src/bcf/bcf/xsd/visinfo.xsd
rename to src/bcf/bcf/v2/xsd/visinfo.xsd
diff --git a/src/bcf/bcf/v3/bcfapi.py b/src/bcf/bcf/v3/bcfapi.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/bcf/bcf/v3/bcfxml.py b/src/bcf/bcf/v3/bcfxml.py
new file mode 100644
index 0000000000..3b3fcc8466
--- /dev/null
+++ b/src/bcf/bcf/v3/bcfxml.py
@@ -0,0 +1,827 @@
+import os
+import uuid
+import shutil
+import zipfile
+import logging
+import tempfile
+import bcf.v3.data
+from datetime import datetime
+from xml.dom import minidom
+from xmlschema import XMLSchema
+from contextlib import contextmanager
+from shutil import copyfile
+
+cwd = os.path.dirname(os.path.realpath(__file__))
+
+
+@contextmanager
+def cd(newdir):
+ prevdir = os.getcwd()
+ os.chdir(os.path.expanduser(newdir))
+ try:
+ yield
+ finally:
+ os.chdir(prevdir)
+
+
+class BcfXml:
+ def __init__(self):
+ self.filepath = None
+ self.logger = logging.getLogger("bcfxml")
+ self.author = "john@doe.com"
+ self.project = bcf.v3.data.Project()
+ self.version = "3.0"
+ self.topics = {}
+
+ def new_project(self):
+ self.project.project_id = str(uuid.uuid4())
+ self.project.name = "New Project"
+ self.topics = {}
+ if self.filepath:
+ self.close_project()
+ self.filepath = tempfile.mkdtemp()
+ self.edit_project()
+ self.edit_version()
+
+ def get_project(self, filepath=None):
+ if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
+ data = self._read_xml("project.bcfp", "project.xsd")
+ self.project.project_id = data["Project"]["@ProjectId"]
+ self.project.name = data["Project"].get("Name")
+ return self.project
+
+ def edit_project(self):
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "ProjectInfo")
+ project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
+ if self.project.name:
+ self._create_element(project, "Name", text=self.project.name)
+ with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def save_project(self, filepath):
+ with cd(self.filepath):
+ zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
+ for root, dirs, files in os.walk("./"):
+ for file in files:
+ zip_file.write(os.path.join(root, file))
+ zip_file.close()
+
+ def get_version(self):
+ data = self._read_xml("bcf.version", "version.xsd")
+ self.version = data["@VersionId"]
+ return self.version
+
+ def edit_version(self):
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "Version", {"VersionId": self.version})
+ with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def get_topics(self):
+ self.topics = {}
+ topics = []
+ subdirs = []
+ for (dirpath, dirnames, filenames) in os.walk(self.filepath):
+ subdirs = dirnames
+ break
+ for subdir in subdirs:
+ try:
+ uuid.UUID(subdir)
+ except ValueError:
+ continue
+ if not os.path.exists(os.path.join(self.filepath, subdir, "markup.bcf")):
+ continue
+ self.topics[subdir] = self.get_topic(subdir)
+ return self.topics
+
+ def get_header(self, guid):
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ if "Header" not in data:
+ return
+ header = bcf.v3.data.Header()
+ if data["Header"].get("Files"):
+ for item in data["Header"]["Files"].get("File", []):
+ header_file = bcf.v3.data.HeaderFile()
+ optional_keys = {
+ "filename": "Filename",
+ "date": "Date",
+ "reference": "Reference",
+ "ifc_project": "@IfcProject",
+ "ifc_spatial_structure_element": "@IfcSpatialStructureElement",
+ "is_external": "@IsExternal",
+ }
+ for key, value in optional_keys.items():
+ if value in item:
+ setattr(header_file, key, item[value])
+ header.files.append(header_file)
+ self.topics[guid].header = header
+ return header
+
+ def get_topic(self, guid):
+ if guid in self.topics:
+ return self.topics[guid]
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ topic = bcf.v3.data.Topic()
+ self.topics[guid] = topic
+
+ mandatory_keys = {
+ "guid": "@Guid",
+ "title": "Title",
+ "creation_date": "CreationDate",
+ "creation_author": "CreationAuthor",
+ "topic_status": "@TopicStatus",
+ "topic_type": "@TopicType",
+ }
+ for key, value in mandatory_keys.items():
+ setattr(topic, key, data["Topic"][value])
+
+ optional_keys = {
+ "priority": "Priority",
+ "index": "Index",
+ "modified_date": "ModifiedDate",
+ "modified_author": "ModifiedAuthor",
+ "due_date": "DueDate",
+ "assigned_to": "AssignedTo",
+ "stage": "Stage",
+ "description": "Description",
+ "server_assigned_id": "@ServerAssignedId",
+ }
+ for key, value in optional_keys.items():
+ if value in data["Topic"]:
+ setattr(topic, key, data["Topic"][value])
+
+ if "ReferenceLinks" in data["Topic"]:
+ topic.reference_links.extend(data["Topic"]["ReferenceLinks"].get("ReferenceLink", []))
+
+ if "Labels" in data["Topic"]:
+ topic.labels.extend(data["Topic"]["Labels"].get("Label", []))
+
+ if "BimSnippet" in data["Topic"]:
+ bim_snippet = bcf.v3.data.BimSnippet()
+ keys = {
+ "snippet_type": "@SnippetType",
+ "is_external": "@IsExternal",
+ "reference": "Reference",
+ "reference_schema": "ReferenceSchema",
+ }
+ for key, value in keys.items():
+ if value in data["Topic"]["BimSnippet"]:
+ setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
+ topic.bim_snippet = bim_snippet
+
+ if data["Topic"].get("DocumentReferences"):
+ for item in data["Topic"]["DocumentReferences"].get("DocumentReference", []):
+ document_reference = bcf.v3.data.DocumentReference()
+ keys = {
+ "document_guid": "DocumentGuid",
+ "url": "Url",
+ "guid": "@Guid",
+ "description": "Description",
+ }
+ for key, value in keys.items():
+ if value in item:
+ setattr(document_reference, key, item[value])
+ topic.document_references.append(document_reference)
+
+ if data["Topic"].get("RelatedTopics"):
+ for item in data["Topic"]["RelatedTopics"].get("RelatedTopic", []):
+ related_topic = bcf.v3.data.RelatedTopic()
+ related_topic.guid = item["@Guid"]
+ topic.related_topics.append(related_topic)
+ return topic
+
+ def add_topic(self, topic=None):
+ if topic is None:
+ topic = bcf.v3.data.Topic()
+ if not topic.guid:
+ topic.guid = str(uuid.uuid4())
+ if not topic.title:
+ topic.title = "New Topic"
+ os.mkdir(os.path.join(self.filepath, topic.guid))
+ self.edit_topic(topic)
+ return topic
+
+ def edit_topic(self, topic):
+ if not topic.creation_date:
+ topic.creation_date = datetime.utcnow().isoformat()
+ topic.creation_author = self.author
+ else:
+ topic.modified_date = datetime.utcnow().isoformat()
+ topic.modified_author = self.author
+
+ self.document = minidom.Document()
+ root = self._create_element(self.document, "Markup")
+ if topic.header:
+ self.write_header(topic.header, root)
+
+ topic_el = self._create_element(
+ root,
+ "Topic",
+ {
+ "Guid": topic.guid,
+ "ServerAssignedId": topic.server_assigned_id,
+ "TopicType": topic.topic_type,
+ "TopicStatus": topic.topic_status,
+ },
+ )
+ if topic.reference_links:
+ reference_Links_el = self._create_element(topic_el, "ReferenceLinks")
+ for reference_link in topic.reference_links:
+ self._create_element(reference_Links_el, "ReferenceLink", text=reference_link)
+
+ text_map = {
+ "Title": topic.title,
+ "Priority": topic.priority,
+ "Index": topic.index,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(topic_el, key, text=value)
+ if topic.labels:
+ label_el = self._create_element(topic_el, "Labels")
+ for label in topic.labels:
+ self._create_element(label_el, "Label", text=label)
+
+ text_map = {
+ "CreationDate": topic.creation_date,
+ "CreationAuthor": topic.creation_author,
+ "ModifiedDate": topic.modified_date,
+ "ModifiedAuthor": topic.modified_author,
+ "DueDate": topic.due_date,
+ "AssignedTo": topic.assigned_to,
+ "Stage": topic.stage,
+ "Description": topic.description,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(topic_el, key, text=value)
+
+ if topic.bim_snippet:
+ bim_snippet = self._create_element(
+ topic_el,
+ "BimSnippet",
+ {
+ "SnippetType": topic.bim_snippet.snippet_type,
+ "IsExternal": topic.bim_snippet.is_external,
+ },
+ )
+ self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
+ self._create_element(
+ bim_snippet,
+ "ReferenceSchema",
+ text=topic.bim_snippet.reference_schema,
+ )
+ if topic.document_references:
+ reference_el = self._create_element(topic_el, "DocumentReferences")
+ self.write_document_references(topic.document_references, reference_el)
+ if topic.related_topics:
+ related_topic_el = self._create_element(topic_el, "RelatedTopics")
+ for related_topic in topic.related_topics:
+ self._create_element(related_topic_el, "RelatedTopic", {"Guid": related_topic.guid})
+ if topic.comments:
+ comment_el = self._create_element(topic_el, "Comments")
+ self.write_comments(topic.comments, comment_el)
+ if topic.viewpoints:
+ viewpoint_el = self._create_element(topic_el, "Viewpoints")
+ self.write_viewpoints(topic.viewpoints, viewpoint_el, topic)
+ with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
+ f.write(self.document.toprettyxml(encoding="utf-8"))
+
+ def write_document_references(self, references, root):
+ for reference in references:
+ document_reference_el = self._create_element(root, "DocumentReference", {"Guid": reference.guid})
+ if reference.document_guid:
+ self._create_element(document_reference_el, "DocumentGuid", text=reference.document_guid)
+ elif reference.url:
+ self._create_element(document_reference_el, "Url", text=reference.url)
+ if reference.description:
+ self._create_element(document_reference_el, "Description", text=reference.description)
+
+ def write_header(self, header, root):
+ if not header or not header.files:
+ return
+ header_el = self._create_element(root, "Header")
+ files_el = self._create_element(header_el, "Files")
+ for f in header.files:
+ file_el = self._create_element(
+ files_el,
+ "File",
+ {
+ "IfcProject": f.ifc_project,
+ "IfcSpatialStructureElement": f.ifc_spatial_structure_element,
+ "IsExternal": f.is_external,
+ },
+ )
+ if f.filename:
+ self._create_element(file_el, "Filename", text=f.filename)
+ if f.date:
+ self._create_element(file_el, "Date", text=f.date)
+ if f.reference:
+ self._create_element(file_el, "Reference", text=f.reference)
+
+ def write_comments(self, comments, root):
+ for comment in comments.values():
+ comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
+ text_map = {
+ "Date": comment.date,
+ "Author": comment.author,
+ "Comment": comment.comment,
+ "ModifiedDate": comment.modified_date,
+ "ModifiedAuthor": comment.modified_author,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(comment_el, key, text=value)
+ if comment.viewpoint:
+ self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
+
+ def add_comment(self, topic, comment=None):
+ if comment is None:
+ comment = bcf.v3.data.Comment()
+ if not comment.guid:
+ comment.guid = str(uuid.uuid4())
+ if not comment.comment:
+ comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
+ topic.comments[comment.guid] = comment
+ self.edit_comment(comment, topic)
+
+ def edit_comment(self, comment, topic):
+ if not comment.date:
+ comment.date = datetime.utcnow().isoformat()
+ comment.author = self.author
+ else:
+ comment.modified_date = datetime.utcnow().isoformat()
+ comment.modified_author = self.author
+ self.edit_topic(topic)
+
+ def delete_comment(self, guid, topic):
+ if guid in topic.comments:
+ del topic.comments[guid]
+ self.edit_topic(topic)
+
+ def delete_topic(self, guid):
+ if guid in self.topics:
+ del self.topics[guid]
+ shutil.rmtree(os.path.join(self.filepath, guid))
+
+ def write_viewpoints(self, viewpoints, root, topic):
+ for viewpoint in viewpoints.values():
+ viewpoint_el = self._create_element(root, "ViewPoint", {"Guid": viewpoint.guid})
+ text_map = {
+ "Viewpoint": viewpoint.viewpoint,
+ "Snapshot": viewpoint.snapshot,
+ "Index": viewpoint.index,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(viewpoint_el, key, text=value)
+ self.write_viewpoint(viewpoint, topic)
+
+ def write_viewpoint(self, viewpoint, topic):
+ document = minidom.Document()
+ root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
+ self.write_viewpoint_components(viewpoint, root)
+ self.write_viewpoint_orthogonal_camera(viewpoint, root)
+ self.write_viewpoint_perspective_camera(viewpoint, root)
+ self.write_viewpoint_lines(viewpoint, root)
+ self.write_viewpoint_clipping_planes(viewpoint, root)
+ self.write_viewpoint_bitmaps(viewpoint, root)
+ with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
+ f.write(document.toprettyxml(encoding="utf-8"))
+
+ def write_viewpoint_components(self, viewpoint, parent):
+ if not viewpoint.components:
+ return
+ components_el = self._create_element(parent, "Components")
+ if viewpoint.components.selection:
+ selection_el = self._create_element(components_el, "Selection")
+ for selection in viewpoint.components.selection:
+ self.write_component(selection, selection_el)
+ if viewpoint.components.visibility:
+ visibility = self._create_element(
+ components_el,
+ "Visibility",
+ {"DefaultVisibility": viewpoint.components.visibility.default_visibility},
+ )
+ if viewpoint.components.visibility.view_setup_hints:
+ view_setup_hints = self._create_element(
+ visibility,
+ "ViewSetupHints",
+ {
+ "SpacesVisible": viewpoint.components.visibility.view_setup_hints.spaces_visible,
+ "SpaceBoundariesVisible": viewpoint.components.visibility.view_setup_hints.space_boundaries_visible,
+ "OpeningsVisible": viewpoint.components.visibility.view_setup_hints.openings_visible,
+ },
+ )
+ if viewpoint.components.visibility.exceptions:
+ exceptions_el = self._create_element(visibility, "Exceptions")
+ for exception in viewpoint.components.visibility.exceptions:
+ self.write_component(exception, exceptions_el)
+ if viewpoint.components.coloring:
+ coloring_el = self._create_element(components_el, "Coloring")
+ for color in viewpoint.components.coloring:
+ color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
+ component_el = self._create_element(color_el, "Components")
+ for component in color.components:
+ self.write_component(component, component_el)
+
+ def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
+ if not viewpoint.orthogonal_camera:
+ return
+ camera = viewpoint.orthogonal_camera
+ camera_el = self._create_element(parent, "OrthogonalCamera")
+ camera_view_point = self._create_element(camera_el, "CameraViewPoint")
+ self.write_vector(camera_view_point, camera.camera_view_point)
+ camera_direction = self._create_element(camera_el, "CameraDirection")
+ self.write_vector(camera_direction, camera.camera_direction)
+ camera_up_vector = self._create_element(camera_el, "CameraUpVector")
+ self.write_vector(camera_up_vector, camera.camera_up_vector)
+ self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
+ self._create_element(camera_el, "AspectRatio", text=camera.aspect_ratio)
+
+ def write_viewpoint_perspective_camera(self, viewpoint, parent):
+ if not viewpoint.perspective_camera:
+ return
+ camera = viewpoint.perspective_camera
+ camera_el = self._create_element(parent, "PerspectiveCamera")
+ camera_view_point = self._create_element(camera_el, "CameraViewPoint")
+ self.write_vector(camera_view_point, camera.camera_view_point)
+ camera_direction = self._create_element(camera_el, "CameraDirection")
+ self.write_vector(camera_direction, camera.camera_direction)
+ camera_up_vector = self._create_element(camera_el, "CameraUpVector")
+ self.write_vector(camera_up_vector, camera.camera_up_vector)
+ self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
+ self._create_element(camera_el, "AspectRatio", text=camera.aspect_ratio)
+
+ def write_viewpoint_lines(self, viewpoint, parent):
+ if not viewpoint.lines:
+ return
+ lines_el = self._create_element(parent, "Lines")
+ for line in viewpoint.lines:
+ line_el = self._create_element(lines_el, "Line")
+ start_point_el = self._create_element(line_el, "StartPoint")
+ self.write_vector(start_point_el, line.start_point)
+ end_point_el = self._create_element(line_el, "EndPoint")
+ self.write_vector(end_point_el, line.end_point)
+
+ def write_viewpoint_clipping_planes(self, viewpoint, parent):
+ if not viewpoint.clipping_planes:
+ return
+ planes_el = self._create_element(parent, "ClippingPlanes")
+ for plane in viewpoint.clipping_planes:
+ plane_el = self._create_element(planes_el, "ClippingPlane")
+ location_el = self._create_element(plane_el, "Location")
+ self.write_vector(location_el, plane.location)
+ direction_el = self._create_element(plane_el, "Direction")
+ self.write_vector(direction_el, plane.direction)
+
+ def write_viewpoint_bitmaps(self, viewpoint, parent):
+ if not viewpoint.bitmaps:
+ return
+ bitmaps_el = self._create_element(parent, "Bitmaps")
+ for bitmap in viewpoint.bitmaps:
+ bitmap_el = self._create_element(bitmaps_el, "Bitmap")
+
+ text_map = {"Format": bitmap.bitmap_format, "Reference": bitmap.reference}
+ for key, value in text_map.items():
+ self._create_element(bitmap_el, key, text=value)
+
+ location_el = self._create_element(bitmap_el, "Location")
+ self.write_vector(location_el, bitmap.location)
+ normal_el = self._create_element(bitmap_el, "Normal")
+ self.write_vector(normal_el, bitmap.normal)
+ up_el = self._create_element(bitmap_el, "Up")
+ self.write_vector(up_el, bitmap.up)
+
+ self._create_element(bitmap_el, "Height", text=bitmap.height)
+
+ def write_vector(self, parent, from_obj):
+ self._create_element(parent, "X", text=from_obj.x)
+ self._create_element(parent, "Y", text=from_obj.y)
+ self._create_element(parent, "Z", text=from_obj.z)
+
+ def write_component(self, data, parent):
+ component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
+ text_map = {
+ "OriginatingSystem": data.originating_system,
+ "AuthoringToolId": data.authoring_tool_id,
+ }
+ for key, value in text_map.items():
+ if value:
+ self._create_element(component_el, key, text=value)
+
+ def add_viewpoint(self, topic, viewpoint=None):
+ if not viewpoint:
+ viewpoint = bcf.v3.data.Viewpoint()
+ if not viewpoint.guid:
+ viewpoint.guid = str(uuid.uuid4())
+ if not viewpoint.viewpoint:
+ viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
+ if viewpoint.snapshot:
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filepath = os.path.join(topic_filepath, viewpoint.snapshot)
+ if not os.path.exists(filepath):
+ filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
+ copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
+ viewpoint.snapshot = filename
+ topic.viewpoints[viewpoint.guid] = viewpoint
+ self.edit_topic(topic)
+
+ def delete_viewpoint(self, guid, topic):
+ if guid not in topic.viewpoints:
+ return
+ viewpoint = topic.viewpoints[guid]
+ if viewpoint.snapshot:
+ filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ if viewpoint.viewpoint:
+ filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ for bitmap in viewpoint.bitmaps:
+ if not bitmap.reference:
+ continue
+ filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ del topic.viewpoints[guid]
+ self.edit_topic(topic)
+
+ def delete_file(self, topic, index):
+ if not topic.header:
+ return
+ f = topic.header.files.pop(index)
+ filepath = os.path.join(self.filepath, topic.guid, f.reference)
+ if not f.is_external and os.path.exists(filepath):
+ os.remove(filepath)
+ self.edit_topic(topic)
+
+ def delete_bim_snippet(self, topic):
+ if not topic.bim_snippet:
+ return
+ if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
+ filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ topic.bim_snippet = None
+ self.edit_topic(topic)
+
+ def delete_document_reference(self, topic, index):
+ document_reference = topic.document_references[index]
+ if document_reference.referenced_document and not document_reference.is_external:
+ filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ del topic.document_references[index]
+ self.edit_topic(topic)
+
+ def add_document_reference(self, topic, document_reference):
+ if os.path.exists(document_reference.referenced_document):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filename = os.path.basename(document_reference.referenced_document)
+ copyfile(
+ document_reference.referenced_document,
+ os.path.join(topic_filepath, filename),
+ )
+ document_reference.referenced_document = filename
+ document_reference.is_external = False
+ else:
+ document_reference.is_external = True
+ if not document_reference.guid:
+ document_reference.guid = str(uuid.uuid4())
+ topic.document_references.append(document_reference)
+ self.edit_topic(topic)
+
+ def add_bim_snippet(self, topic, bim_snippet):
+ if topic.bim_snippet:
+ self.delete_bim_snippet(topic)
+ if os.path.exists(bim_snippet.reference):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ filename = os.path.basename(bim_snippet.reference)
+ copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
+ bim_snippet.reference = filename
+ bim_snippet.is_external = False
+ else:
+ bim_snippet.is_external = True
+ topic.bim_snippet = bim_snippet
+ self.edit_topic(topic)
+
+ def add_file(self, topic, header_file):
+ if os.path.exists(header_file.reference):
+ topic_filepath = os.path.join(self.filepath, topic.guid)
+ header_file.filename = os.path.basename(header_file.reference)
+ copyfile(
+ header_file.reference,
+ os.path.join(topic_filepath, header_file.filename),
+ )
+ header_file.reference = header_file.filename
+ header_file.is_external = False
+ header_file.date = datetime.utcnow().isoformat()
+ if not topic.header:
+ topic.header = bcf.v3.data.Header()
+ topic.header.files.append(header_file)
+ self.edit_topic(topic)
+
+ def get_comments(self, guid):
+ comments = {}
+ if "Comments" not in data["Topics"]:
+ return comments
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ for item in data["Topic"]["Comments"].get("Comment", []):
+ comment = bcf.v3.data.Comment()
+ mandatory_keys = {
+ "guid": "@Guid",
+ "date": "Date",
+ "author": "Author",
+ }
+ for key, value in mandatory_keys.items():
+ setattr(comment, key, item[value])
+ optional_keys = {
+ "comment": "Comment",
+ "modified_date": "ModifiedDate",
+ "modified_author": "ModifiedAuthor",
+ }
+ for key, value in optional_keys.items():
+ if value in item:
+ setattr(comment, key, item[value])
+ if "Viewpoint" in item:
+ viewpoint = bcf.v3.data.Viewpoint()
+ viewpoint.guid = item["Viewpoint"]["@Guid"]
+ comment.viewpoint = viewpoint
+ comments[comment.guid] = comment
+ self.topics[guid].comments = comments
+ return comments
+
+ def get_viewpoints(self, guid):
+ viewpoints = {}
+ if "Viewpoints" not in data["Topic"]:
+ return viewpoints
+ data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
+ for item in data["Topic"]["Viewpoints"]:
+ viewpoint = self.get_viewpoint(item, guid)
+ viewpoints[viewpoint.guid] = viewpoint
+ self.topics[guid].viewpoints = viewpoints
+ return viewpoints
+
+ def get_viewpoint(self, data, topic_guid):
+ viewpoint = bcf.v3.data.Viewpoint()
+ viewpoint.guid = data["@Guid"]
+ optional_keys = {
+ "viewpoint": "Viewpoint",
+ "snapshot": "Snapshot",
+ "index": "Index",
+ }
+ for key, value in optional_keys.items():
+ if value in data:
+ setattr(viewpoint, key, data[value])
+ visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
+ viewpoint.components = self.get_viewpoint_components(visinfo)
+ viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
+ viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
+ viewpoint.lines = self.get_viewpoint_lines(visinfo)
+ viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
+ viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
+ return viewpoint
+
+ def get_viewpoint_components(self, visinfo):
+ if "Components" not in visinfo:
+ return None
+ components = bcf.v3.data.Components()
+ data = visinfo["Components"]
+ if "Selection" in data and "Component" in data["Selection"]:
+ for item in data["Selection"].get("Component", []):
+ components.selection.append(self.get_component(item))
+ if "Visibility" in data:
+ component_visibility = bcf.v3.data.ComponentVisibility()
+ if "@DefaultVisibility" in data["Visibility"]:
+ component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
+ if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
+ for item in data["Visibility"]["Exceptions"]["Component"]:
+ component_visibility.exceptions.append(self.get_component(item))
+ if "ViewSetupHints" in data["Visibility"]:
+ view_setup_hints = bcf.v3.data.ViewSetupHints()
+ optional_keys = {
+ "spaces_visible": "@SpacesVisible",
+ "space_boundaries_visible": "@SpaceBoundariesVisible",
+ "openings_visible": "@OpeningsVisible",
+ }
+ for key, value in optional_keys.items():
+ if value in data["Visibility"]["ViewSetupHints"]:
+ setattr(view_setup_hints, key, data["Visibility"]["ViewSetupHints"][value])
+ component_visibility.view_setup_hints = view_setup_hints
+ components.visibility = component_visibility
+ if "Coloring" in data and "Color" in data["Coloring"]:
+ for item in data["Coloring"]["Color"]:
+ color = bcf.v3.data.Color()
+ color.color = item["@Color"]
+ for item2 in item["Components"]["Component"]:
+ color.components.append(self.get_component(item2))
+ components.coloring.append(color)
+ return components
+
+ def get_viewpoint_orthogonal_camera(self, visinfo):
+ if "OrthogonalCamera" not in visinfo:
+ return None
+ camera = bcf.v3.data.OrthogonalCamera()
+ data = visinfo["OrthogonalCamera"]
+ self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
+ self.set_vector(camera.camera_direction, data["CameraDirection"])
+ self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
+ camera.view_to_world_scale = data["ViewToWorldScale"]
+ camera.aspect_ratio = data["AspectRatio"]
+ return camera
+
+ def get_viewpoint_perspective_camera(self, visinfo):
+ if "PerspectiveCamera" not in visinfo:
+ return None
+ camera = bcf.v3.data.PerspectiveCamera()
+ data = visinfo["PerspectiveCamera"]
+ self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
+ self.set_vector(camera.camera_direction, data["CameraDirection"])
+ self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
+ camera.field_of_view = data["FieldOfView"]
+ camera.aspect_ratio = data["AspectRatio"]
+ return camera
+
+ def get_viewpoint_lines(self, visinfo):
+ if "Lines" not in visinfo:
+ return []
+ lines = []
+ for item in visinfo["Lines"].get("Line", []):
+ line = bcf.v3.data.Line()
+ self.set_vector(line.start_point, item["StartPoint"])
+ self.set_vector(line.end_point, item["EndPoint"])
+ lines.append(line)
+ return lines
+
+ def get_viewpoint_clipping_planes(self, visinfo):
+ if "ClippingPlanes" not in visinfo:
+ return []
+ planes = []
+ for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
+ plane = bcf.v3.data.ClippingPlane()
+ self.set_vector(plane.location, item["Location"])
+ self.set_vector(plane.direction, item["Direction"])
+ planes.append(plane)
+ return planes
+
+ def get_viewpoint_bitmaps(self, visinfo):
+ if "Bitmaps" not in visinfo:
+ return []
+ bitmaps = []
+ for item in visinfo["Bitmaps"].get("Bitmap"):
+ bitmap = bcf.v3.data.Bitmap()
+ bitmap.reference = item["Reference"]
+ bitmap.bitmap_format = item["Format"].upper()
+ self.set_vector(bitmap.location, item["Location"])
+ self.set_vector(bitmap.normal, item["Normal"])
+ self.set_vector(bitmap.up, item["Up"])
+ bitmap.height = item["Height"]
+ bitmaps.append(bitmap)
+ return bitmaps
+
+ def set_vector(self, to_obj, from_xml):
+ to_obj.x = from_xml["X"]
+ to_obj.y = from_xml["Y"]
+ to_obj.z = from_xml["Z"]
+
+ def get_component(self, data):
+ component = bcf.v3.data.Component()
+ optional_keys = {
+ "originating_system": "OriginatingSystem",
+ "authoring_tool_id": "AuthoringToolId",
+ "ifc_guid": "@IfcGuid",
+ }
+ for key, value in optional_keys.items():
+ if value in data:
+ setattr(component, key, data[value])
+ return component
+
+ def close_project(self):
+ shutil.rmtree(self.filepath)
+
+ def _read_xml(self, filename, xsd):
+ schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
+ filepath = os.path.join(self.filepath, filename)
+ (data, errors) = schema.to_dict(filepath, validation="lax")
+ for error in errors:
+ self.logger.error(error)
+ return data
+
+ def _create_element(self, parent, name, attributes={}, text=None):
+ element = self.document.createElement(name)
+ for key, value in attributes.items():
+ if isinstance(value, bool):
+ element.setAttribute(key, str(value).lower())
+ elif value:
+ element.setAttribute(key, value)
+ if text is not None:
+ text = self.document.createTextNode(str(text))
+ element.appendChild(text)
+ parent.appendChild(element)
+ return element
+
+ def __del__(self):
+ self.close_project()
diff --git a/src/bcf/bcf/v3/data.py b/src/bcf/bcf/v3/data.py
new file mode 100644
index 0000000000..b6f241e148
--- /dev/null
+++ b/src/bcf/bcf/v3/data.py
@@ -0,0 +1,181 @@
+class Project:
+ def __init__(self):
+ self.project_id = ""
+ self.name = ""
+
+
+class BimSnippet:
+ def __init__(self):
+ self.snippet_type = None
+ self.is_external = False
+ self.reference = None
+ self.reference_schema = None
+
+
+class DocumentReference:
+ def __init__(self):
+ self.description = None
+ self.document_guid = None
+ self.url = None
+ self.guid = None
+
+
+class RelatedTopic:
+ def __init__(self):
+ self.guid = None
+
+
+class HeaderFile:
+ def __init__(self):
+ self.file_name = ""
+ self.date = None
+ self.reference = ""
+ self.ifc_project = None
+ self.ifc_spatial_structure_element = None
+ self.is_external = True
+
+
+class Header:
+ def __init__(self):
+ self.files = []
+
+
+class Topic:
+ def __init__(self):
+ self.reference_links = []
+ self.title = ""
+ self.priority = None
+ self.index = None # Deprecated, stored, but ignored
+ self.labels = []
+ self.creation_date = None
+ self.creation_author = None
+ self.modified_date = None
+ self.modified_author = None
+ self.due_date = None
+ self.assigned_to = None
+ self.stage = None
+ self.description = None
+ self.bim_snippet = None
+ self.document_references = []
+ self.related_topics = []
+ self.topic_status = None
+ self.topic_type = None
+ self.guid = None
+
+ self.header = None
+ self.comments = {}
+ self.viewpoints = {}
+ self.server_assigned_id = ""
+
+
+class Comment:
+ def __init__(self):
+ self.guid = None
+ self.date = None
+ self.author = ""
+ self.comment = ""
+ self.viewpoint = None
+ self.modified_date = None
+ self.modified_author = ""
+
+
+class ViewSetupHints:
+ def __init__(self):
+ self.spaces_visible = False
+ self.space_boundaries_visible = False
+ self.openings_visible = False
+
+
+class Component:
+ def __init__(self):
+ self.originating_system = None
+ self.authoring_tool_id = None
+ self.ifc_guid = None
+
+
+class ComponentVisibility:
+ def __init__(self):
+ self.exceptions = []
+ self.default_visibility = False
+ self.view_setup_hints = None
+
+
+class Color:
+ def __init__(self):
+ self.color = None
+ self.components = []
+
+
+class Components:
+ def __init__(self):
+
+ self.selection = []
+ self.visibility = None
+ self.coloring = []
+
+
+class Point:
+ def __init__(self):
+ self.x = 0
+ self.y = 0
+ self.z = 0
+
+
+class Direction(Point):
+ pass
+
+
+class OrthogonalCamera:
+ def __init__(self):
+ self.camera_view_point = Point()
+ self.camera_direction = Direction()
+ self.camera_up_vector = Direction()
+ self.view_to_world_scale = 1.0
+ self.aspect_ratio = 1.0
+
+
+class PerspectiveCamera:
+ def __init__(self):
+ self.camera_view_point = Point()
+ self.camera_direction = Direction()
+ self.camera_up_vector = Direction()
+ self.field_of_view = 60.0
+ self.aspect_ratio = 1.0
+
+
+class Line:
+ def __init__(self):
+ self.start_point = Point()
+ self.end_point = Point()
+
+
+class ClippingPlane:
+ def __init__(self):
+ self.location = Point()
+ self.direction = Direction()
+
+
+class Bitmap:
+ def __init__(self):
+ self.reference = "" # Only in BCF-XML
+ self.bitmap_data = None # Only in BCF-API
+ self.bitmap_format = "PNG" # Enum of png or jpg
+ self.location = Point()
+ self.normal = Direction()
+ self.up = Direction()
+ self.height = 1.0
+
+
+class Viewpoint:
+ def __init__(self):
+ self.guid = None
+ self.viewpoint = None
+ self.snapshot = None
+ self.index = None
+
+ self.components = None # It's not a list, despite the plural name
+ self.orthogonal_camera = None
+ self.perspective_camera = None
+ self.lines = []
+ self.clipping_planes = []
+ self.bitmaps = []
diff --git a/src/bcf/bcf/v3/xsd/documents.xsd b/src/bcf/bcf/v3/xsd/documents.xsd
new file mode 100644
index 0000000000..f02331f65c
--- /dev/null
+++ b/src/bcf/bcf/v3/xsd/documents.xsd
@@ -0,0 +1,31 @@
+
+