Merge pull request #1 from IfcOpenShell/v0.6.0

同步源库
This commit is contained in:
GisSpace
2020-12-18 16:23:04 +08:00
committed by GitHub
38 changed files with 2203 additions and 529 deletions
+59
View File
@@ -0,0 +1,59 @@
# bcf
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.
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
```
from bcf.bcfxml import BcfXml
bcfxml = BcfXml()
# Load a project
project = bcfxml.get_project("/path/to/file.bcf")
# The project is also stored in the module
# project == bcfxml.project
print(project.name)
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
bcfxml.edit_project()
# The BCF file is extracted to this temporary directory
print(bcfxml.filepath)
# Get a dictionary of topics
topics = bcfxml.get_topics()
# Note: topics == bcfxml.topics
for guid, topic in bcfxml.topics.items():
print("Topic guid is", guid)
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# Fetch extra data about a topic
header = bcfxml.get_header(guid)
comments = bcfxml.get_comments(guid)
viewpoints = bcfxml.get_viewpoints(guid)
# Note: comments == topic.comments, and so on
for comment_guid, comment in comments.items():
print(comment_guid)
print(comment.comment)
print(comment.author)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Modify a topic
topic.title = "New title"
bcfxml.edit_topic(topic)
```
+675
View File
@@ -0,0 +1,675 @@
import os
import uuid
import shutil
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
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.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)
data = self._read_xml("project.bcfp", "project.xsd")
self.project.project_id = data["Project"]["@ProjectId"]
self.project.name = data["Project"]["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
else:
topic.modified_date = datetime.utcnow().isoformat()
topic.modified_author = self.author
self.document = minidom.Document()
root = self._create_element(self.document, "Markup")
topic_el = self._create_element(
root,
"Topic",
{
"Guid": topic.guid,
"TopicType": topic.topic_type,
"TopicStatus": topic.topic_status,
},
)
text_map = {
"Title": topic.title,
"Priority": topic.priority,
"Index": topic.index,
"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)
for reference_link in topic.reference_links:
self._create_element(topic_el, "ReferenceLink", text=reference_link)
for label in topic.labels:
self._create_element(topic_el, "Labels", text=label)
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_header(topic.header, root)
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:
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, 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'."
self.edit_comment(comment)
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")
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,
"SpaceBoundiresVisible": 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, "Height": bitmap.height}
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)
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):
pass # TODO: handle uploading files
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 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"].lower()
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()
+178
View File
@@ -0,0 +1,178 @@
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.referenced_document = None
self.description = None
self.guid = None
self.is_external = False
class RelatedTopic:
def __init__(self):
self.guid = None
class HeaderFile:
def __init__(self):
self.filename = None
self.date = None
self.reference = None
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 = {}
class Comment:
def __init__(self):
self.guid = None
self.date = None
self.author = None
self.comment = None
self.viewpoint = None
self.modified_date = None
self.modified_author = None
self.topic_guid = None # Part of BCF-API
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
class Color:
def __init__(self):
self.color = None
self.components = []
class Components:
def __init__(self):
self.view_setup_hints = None
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
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
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_type = "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 = []
+154
View File
@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Markup">
<xs:complexType>
<xs:sequence>
<xs:element name="Header" type="Header" minOccurs="0"/>
<xs:element name="Topic" type="Topic"/>
<xs:element name="Comment" type="Comment" minOccurs="0" maxOccurs="unbounded"/>
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
<xs:element name="Viewpoints" type="ViewPoint" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Header">
<xs:sequence>
<xs:element name="File" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Filename" type="xs:string" minOccurs="0"/>
<xs:element name="Date" type="xs:dateTime" minOccurs="0"/>
<!-- Reference (URL) of the file -->
<xs:element name="Reference" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attributeGroup ref="FileAttributes"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
<xs:complexType name="ViewPoint">
<xs:sequence>
<!-- viewpoint file (xml) -->
<xs:element name="Viewpoint" type="xs:string" minOccurs="0"/>
<!-- the snapshot png -->
<xs:element name="Snapshot" type="xs:string" minOccurs="0"/>
<!-- the viewpoint index (sort order) -->
<xs:element name="Index" type="xs:int" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Guid" type="Guid" use="required"/>
<!-- Guid of the viewpoint -->
</xs:complexType>
<!-- BimSnippet -->
<xs:complexType name="BimSnippet">
<xs:sequence>
<!--
Name of the file in the topic folder containing the snippet or a URL.
E.G.- Expresscode containing p.e Issue, Request
// Maybe some header infos ?? // IfcEntites // Geometry
-->
<!-- Reference (name) to the snippet file -->
<xs:element name="Reference" type="xs:string"/>
<xs:element name="ReferenceSchema" type="xs:string"/>
</xs:sequence>
<xs:attribute name="SnippetType" type="xs:string" use="required"/>
<xs:attribute name="isExternal" type="xs:boolean" default="false"/>
<!-- This flag is true when the reference is a URL pointing outside of the BCF file-->
</xs:complexType>
<xs:complexType name="Topic">
<xs:sequence>
<xs:element name="ReferenceLink" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Priority" type="Priority" minOccurs="0"/>
<!-- ISG Jira issue BCF-8 Add a way save order the topics -->
<xs:element name="Index" type="xs:int" minOccurs="0"/>
<xs:element name="Labels" type="TopicLabel" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="CreationDate" type="xs:dateTime" minOccurs="1"/>
<xs:element name="CreationAuthor" type="UserIdType" minOccurs="1"/>
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
<xs:element name="ModifiedAuthor" type="UserIdType" minOccurs="0"/>
<xs:element name="DueDate" type="xs:dateTime" minOccurs="0"/>
<xs:element name="AssignedTo" type="UserIdType" minOccurs="0"/>
<xs:element name="Stage" type="Stage" minOccurs="0"/>
<xs:element name="Description" type="xs:string" minOccurs="0"/>
<xs:element name="BimSnippet" type="BimSnippet" minOccurs="0"/>
<!-- Name of the file in the topic folder or url -->
<xs:element name="DocumentReference" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<!-- Name of the file in the topic folder or url -->
<xs:element name="ReferencedDocument" type="xs:string" minOccurs="0"/>
<!-- Human readable name of the document -->
<xs:element name="Description" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attributeGroup ref="DocumentReference"/>
</xs:complexType>
</xs:element>
<xs:element name="RelatedTopic" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="Guid" type="Guid" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Guid" type="Guid" use="required"/>
<xs:attribute name="TopicType" type="TopicType"/>
<xs:attribute name="TopicStatus" type="TopicStatus"/>
</xs:complexType>
<!-- Reference to a document inside of the topic folder or a url pointing to the web -->
<xs:attributeGroup name="DocumentReference">
<!-- Guid of the DocumentReference -->
<xs:attribute name="Guid" type="Guid"/>
<!-- A flag that is true when the ReferencedDocument points outside of the BCF file (a URL) -->
<xs:attribute name="isExternal" type="xs:boolean" default="false"/>
</xs:attributeGroup>
<xs:complexType name="Comment">
<xs:sequence>
<xs:element name="Date" type="xs:dateTime"/>
<xs:element name="Author" type="UserIdType"/>
<xs:element name="Comment" type="xs:string"/>
<xs:element name="Viewpoint" minOccurs="0">
<xs:complexType>
<xs:attribute name="Guid" type="Guid" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
<xs:element name="ModifiedAuthor" type="UserIdType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Guid" type="Guid" use="required"/>
</xs:complexType>
<xs:simpleType name="TopicStatus">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="TopicType">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="TopicLabel">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="Priority">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="UserIdType">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="Stage">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="Guid">
<xs:restriction base="xs:string">
<xs:pattern value="[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="IfcGuid">
<xs:restriction base="xs:string">
<xs:length value="22"/>
<xs:pattern value="[0-9,A-Z,a-z,_$]*"/>
</xs:restriction>
</xs:simpleType>
<xs:attributeGroup name="FileAttributes">
<xs:attribute name="IfcProject" type="IfcGuid"/>
<xs:attribute name="IfcSpatialStructureElement" type="IfcGuid"/>
<xs:attribute name="isExternal" type="xs:boolean" default="true"/>
</xs:attributeGroup>
</xs:schema>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ProjectExtension">
<xs:complexType>
<xs:sequence>
<xs:element name="Project" type="Project" minOccurs="0"/>
<xs:element name="ExtensionSchema" type="xs:anyURI"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Project">
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="ProjectId" type="xs:string" use="required"/>
</xs:complexType>
</xs:schema>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mit XMLSpy v2011 rel. 3 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Version">
<xs:complexType>
<xs:sequence>
<xs:element name="DetailedVersion" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="VersionId" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>
+191
View File
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="VisualizationInfo">
<xs:annotation>
<xs:documentation>VisualizationInfo documentation</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Components" type="Components" minOccurs="0"/>
<xs:element name="OrthogonalCamera" type="OrthogonalCamera" minOccurs="0"/>
<xs:element name="PerspectiveCamera" type="PerspectiveCamera" minOccurs="0"/>
<xs:element name="Lines" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="Line" type="Line" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ClippingPlanes" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="ClippingPlane" type="ClippingPlane" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- ISG Jira issue BCF-17: Add support for text in the viewpoints -->
<xs:element name="Bitmap" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Bitmap" type="BitmapFormat"/>
<!-- Name of the bitmap file in the topic folder -->
<xs:element name="Reference" type="xs:string"/>
<!-- Location of the center of the bitmap -->
<xs:element name="Location" type="Point"/>
<!-- Normal of the bitmap -->
<xs:element name="Normal" type="Direction"/>
<!-- Upvector of the bitmap -->
<xs:element name="Up" type="Direction"/>
<!-- Height of the bitmap -->
<xs:element name="Height" type="xs:double"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<!-- Guid of the viewpoint -->
<xs:attribute name="Guid" type="Guid" use="required"/>
</xs:complexType>
</xs:element>
<xs:complexType name="OrthogonalCamera">
<xs:sequence>
<xs:element name="CameraViewPoint" type="Point"/>
<xs:element name="CameraDirection" type="Direction"/>
<xs:element name="CameraUpVector" type="Direction"/>
<xs:element name="ViewToWorldScale" type="xs:double">
<xs:annotation>
<xs:documentation>view's visible size in meters</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PerspectiveCamera">
<xs:sequence>
<xs:element name="CameraViewPoint" type="Point"/>
<xs:element name="CameraDirection" type="Direction"/>
<xs:element name="CameraUpVector" type="Direction"/>
<xs:element name="FieldOfView" type="FieldOfView">
<xs:annotation>
<xs:documentation>
It is currently limited to a value between 45 and 60 degrees.
This limitation will be dropped in the next release and viewers
should be expect values outside this range in current implementations.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Point">
<xs:sequence>
<xs:element name="X" type="xs:double"/>
<xs:element name="Y" type="xs:double"/>
<xs:element name="Z" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Direction">
<xs:sequence>
<xs:element name="X" type="xs:double"/>
<xs:element name="Y" type="xs:double"/>
<xs:element name="Z" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="FieldOfView">
<xs:restriction base="xs:double">
<xs:minInclusive value="45"/>
<xs:maxInclusive value="60"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Components">
<xs:sequence>
<xs:element name="ViewSetupHints" type="ViewSetupHints" minOccurs="0" />
<!-- Components with relevance to the viewpoint. They should be displayed highlighted or selected in a viewer -->
<xs:element name="Selection" type="ComponentSelection" minOccurs="0" />
<xs:element name="Visibility" type="ComponentVisibility" minOccurs="1" />
<xs:element name="Coloring" type="ComponentColoring" minOccurs="0" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ViewSetupHints">
<xs:attribute name="SpacesVisible" type="xs:boolean"/>
<xs:attribute name="SpaceBoundariesVisible" type="xs:boolean"/>
<xs:attribute name="OpeningsVisible" type="xs:boolean"/>
</xs:complexType>
<xs:complexType name="ComponentSelection">
<xs:sequence>
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ComponentVisibility">
<xs:sequence>
<xs:element name="Exceptions" minOccurs="0">
<!-- List Components that are different than the DefaultVisibility. E.g. if DefaultVisibility = false then list
Components that should be visible -->
<xs:complexType>
<xs:sequence>
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="DefaultVisibility" type="xs:boolean"/>
</xs:complexType>
<xs:complexType name="ComponentColoring">
<xs:sequence>
<xs:element name="Color" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="Color"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Component">
<xs:sequence>
<xs:element name="OriginatingSystem" type="xs:string" minOccurs="0"/>
<xs:element name="AuthoringToolId" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute ref="IfcGuid"/>
<!-- ISG Jira Issue BCF-14 -->
</xs:complexType>
<xs:attribute name="Color">
<xs:simpleType>
<xs:restriction base="xs:normalizedString">
<!-- Should either match 3 or 4 hex bytes , e.g. "FF00FF" or "FF00FF99" -->
<xs:pattern value="[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="IfcGuid">
<xs:simpleType>
<xs:restriction base="xs:normalizedString">
<xs:length value="22"/>
<xs:pattern value="[0-9,A-Z,a-z,_$]*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:complexType name="Line">
<xs:sequence>
<xs:element name="StartPoint" type="Point"/>
<xs:element name="EndPoint" type="Point"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ClippingPlane">
<xs:sequence>
<xs:element name="Location" type="Point"/>
<xs:element name="Direction" type="Direction"/>
</xs:sequence>
</xs:complexType>
<!-- ISG Jira issue BCF-17: Add support for text in the viewpoints -->
<xs:simpleType name="BitmapFormat">
<xs:restriction base="xs:string">
<xs:enumeration value="PNG"/>
<xs:enumeration value="JPG"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="Guid">
<xs:restriction base="xs:string">
<xs:pattern value="[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
@@ -1,6 +1,9 @@
import gettext
from behave import step
from ifcdata_methods import assert_schema
from utils import IfcFile
from utils import switch_locale
@step('The IFC file "{file}" must be provided')
@@ -13,10 +16,9 @@ def step_impl(context, file):
@step("IFC data must use the {schema} schema")
def step_impl(context, schema):
assert IfcFile.get().schema == schema, "We expected a schema of {} but instead got {}".format(
schema, IfcFile.get().schema
)
switch_locale(context.localedir, "en")
assert_schema(context, schema)
@step('The IFC file "{file}" is exempt from being provided')
def step_impl(context, file):
@@ -0,0 +1,9 @@
from behave import step
from ifcdata_methods import assert_schema
from utils import switch_locale
@step("Die IFC Daten müssen das {schema} Schema benutzen")
def step_impl(context, schema):
switch_locale(context.localedir, "de")
assert_schema(context, schema)
@@ -0,0 +1,9 @@
from behave import step
from ifcdata_methods import assert_schema
from utils import switch_locale
@step("Les données IFC doivent utiliser le schéma {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, "fr")
assert_schema(context, schema)
@@ -0,0 +1,9 @@
from utils import IfcFile
def assert_schema(context, target_schema):
real_schema = IfcFile.get().schema
assert real_schema == target_schema, (
_("We expected a schema of {} but instead got {}")
.format(target_schema, real_schema)
)
@@ -78,3 +78,13 @@ def assert_pset(element, pset_name, prop_name=None, value=None):
assert actual_value == value, 'We expected a value of "{}" but instead got "{}" for the element {}'.format(
value, actual_value, element
)
def switch_locale(locale_dir, locale_id="en"):
from gettext import translation
newlang = translation(
"messages",
localedir=locale_dir,
languages=[locale_id]
)
newlang.install()
@@ -0,0 +1,9 @@
{
"tr_lang": "de",
"tr_success": "Bestanden",
"tr_failure": "Durchgefallen",
"tr_tests_passed": "Erfolgreiche Tests",
"tr_duration": "Dauer",
"tr_auditing": "OpenBIM auditing ist eine Funktionalität von",
"tr_and": "und"
}
@@ -0,0 +1,9 @@
{
"tr_lang": "en",
"tr_success": "Success",
"tr_failure": "Failure",
"tr_tests_passed": "Tests passed",
"tr_duration": "Duration",
"tr_auditing": "OpenBIM auditing is a feature of",
"tr_and": "and"
}
@@ -0,0 +1,9 @@
{
"tr_lang": "fr",
"tr_success": "Succès",
"tr_failure": "Échec",
"tr_tests_passed": "Tests réussis",
"tr_duration": "Durée",
"tr_auditing": "L'audit OpenBIM auditing est une fonctionnalité de",
"tr_and": "et"
}
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang={{tr_lang}}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="foobaro">
<title>BlenderBIM</title>
<title>{{name}}</title>
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
<style>
body { font-family: 'Arial', sans-serif; padding: 40px; }
@@ -30,8 +30,8 @@
<h1>{{name}}</h1>
<p><strong>{{time}} {{file_name}}</strong></p>
<hr>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}Success{{/is_success}}{{^is_success}}Failure{{/is_success}}</span>
Tests passed: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<br />
<p class="description">
{{#description}}
@@ -44,10 +44,10 @@
<section>
<h2>{{name}}</h2>
<p>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}Success{{/is_success}}{{^is_success}}Failure{{/is_success}}</span>
Tests passed: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="time">
Duration: {{time}}s
{{tr_duration}}: {{time}}s
</span>
</p>
<ol>
@@ -70,7 +70,7 @@
<hr>
<footer>
<p>
OpenBIM auditing is a feature of <a href="https://blenderbim.org/">BlenderBIM</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
{{tr_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{tr_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
</p>
</footer>
</body>
@@ -0,0 +1,24 @@
# German translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:11+0100\n"
"Last-Translator: \n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}"
@@ -0,0 +1,24 @@
# German translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:12+0100\n"
"Last-Translator: \n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr "We expected a schema of {} but instead got {}"
@@ -0,0 +1,24 @@
# French translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:12+0100\n"
"Last-Translator: \n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr ""
@@ -0,0 +1,23 @@
# Translations template for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr ""
+39 -6
View File
@@ -7,10 +7,10 @@ import pystache
def generate_report(adir="."):
print("# Generating HTML reports now.")
# get html template
html_template_file = os.path.join(
# get html template path
report_template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"features/template.html"
"features/"
)
# get report file
@@ -93,7 +93,40 @@ def generate_report(adir="."):
data["total_steps"] = sum([s["total_steps"] for s in data["scenarios"]])
data["pass_rate"] = round((data["total_passes"] / data["total_steps"]) * 100)
html_report_file = os.path.join(report_dir, "{}.html".format(file_name))
with open(html_report_file, "w") as out:
with open(html_template_file) as template:
# translate report
# json.dump(mydict, myfile, indent=4)
# workaround for retrieving the feature file language
print(feature["keyword"])
if feature["keyword"] == "Feature":
strings_file_report = os.path.join(
report_template_path,
"strings_template_en.json"
)
elif feature["keyword"] == "Funktionalität":
strings_file_report = os.path.join(
report_template_path,
"strings_template_de.json"
)
elif feature["keyword"] == "Fonctionnalité":
strings_file_report = os.path.join(
report_template_path,
"strings_template_fr.json"
)
else:
# standard English
strings_file_report = os.path.join(
report_template_path,
"strings_template_en.json"
)
strings_report = json.loads(
open(strings_file_report, encoding="utf8").read()
)
# print(strings_report)
data.update(strings_report)
# print(data)
html_report = os.path.join(report_dir, "{}.html".format(file_name))
html_tmpl = os.path.join(report_template_path, "template.html")
with open(html_report, "w", encoding="utf8") as out:
with open(html_tmpl, encoding="utf8") as template:
out.write(pystache.render(template.read(), data))
+81 -32
View File
@@ -11,6 +11,9 @@ from behave.__main__ import main as behave_main
# get bimtester source code module path
bimtester_path = os.path.dirname(os.path.realpath(__file__))
# print(bimtester_path)
locale_path = os.path.join(bimtester_path, "locale")
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
@@ -30,7 +33,16 @@ def run_tests(args):
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
elif not args["console"]:
behave_args.extend(["--format", "json.pretty", "--outfile", "report/report.json"])
behave_args.extend([
"--format",
"json.pretty",
"--outfile",
"report/report.json"
])
behave_args.extend([
"--define",
"localedir={}".format(locale_path)
])
behave_main(behave_args)
print("# All tests are finished.")
return True
@@ -43,7 +55,13 @@ def get_features(args):
if f.endswith(".feature"):
os.remove(os.path.join(features_dir, f))
if args["feature"]:
shutil.copyfile(args["feature"], os.path.join(get_resource_path("features"), os.path.basename(args["feature"])))
shutil.copyfile(
args["feature"],
os.path.join(
get_resource_path("features"),
os.path.basename(args["feature"])
)
)
return True
if os.path.exists("features"):
shutil.copytree("features", get_resource_path("features"))
@@ -55,7 +73,10 @@ def get_features(args):
if args["feature"] and args["feature"] != f:
continue
has_features = True
shutil.copyfile(f, os.path.join(get_resource_path("features"), os.path.basename(f)))
shutil.copyfile(
f,
os.path.join(get_resource_path("features"), os.path.basename(f))
)
return has_features
@@ -111,16 +132,23 @@ def run_intmp_tests(args={}):
"""
from behave import __version__ as behave_version
# https://github.com/behave/behave/issues/871
if behave_version == "1.2.5":
print("At least behave version 1.2.6 is needed, but version {} found.".format(behave_version))
print(
"At least behave version 1.2.6 is needed, but version {} found."
.format(behave_version)
)
return False
# get the features_path, the feature files where the tests are in
if "features" in args and args["features"] != "":
# TODO check if path exists, and if features dir is inside
features_path = os.path.join(args["features"], "features")
if not os.path.isdir(features_path):
print(
"The features directory does not exist: {}"
.format(features_path)
)
return False
else:
# TODO assume features beside ifc thus use ifc path
print("No features path was given.")
@@ -152,7 +180,6 @@ def run_intmp_tests(args={}):
run_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(run_path):
from shutil import rmtree
rmtree(run_path) # fails on read only files
if os.path.isdir(run_path):
print("Delete former beimtester run dir {} failed".format(run_path))
@@ -185,7 +212,10 @@ def run_intmp_tests(args={}):
theline = line
if ifc_filename is None:
ifc_filename = os.path.basename(theline.split('"')[1])
newifcline = ' * The IFC file "{}" must be provided\n'.format(os.path.join(ifc_path, ifc_filename))
newifcline = (
' * The IFC file "{}" must be provided\n'
.format(os.path.join(ifc_path, ifc_filename))
)
# print(newifcline)
break
else:
@@ -199,15 +229,26 @@ def run_intmp_tests(args={}):
print(line.replace(theline, newifcline), end="")
# copy step files and environment file
steps_path = os.path.join(bimtester_path, "features", "steps")
steps_path = os.path.join(
bimtester_path,
"features",
"steps"
)
# print(steps_path)
# print(copy_steps_path)
if os.path.exists(steps_path):
shutil.copytree(steps_path, copy_steps_path)
environment_file = os.path.join(bimtester_path, "features", "environment.py")
environment_file = os.path.join(
bimtester_path,
"features",
"environment.py"
)
if os.path.isfile(environment_file):
shutil.copyfile(environment_file, os.path.join(copy_features_path, "environment.py"))
shutil.copyfile(
environment_file,
os.path.join(copy_features_path, "environment.py")
)
# get advanced args
# print to console from inside step files, add "--no-capture" flag
@@ -216,27 +257,27 @@ def run_intmp_tests(args={}):
if "advanced_arguments" in args:
behave_args.extend(args["advanced_arguments"].split())
elif "console" not in args:
behave_args.extend(
[
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# next two lines are one arg
"--outfile",
os.path.join(report_path, "report.json"),
# next two lines are one arg
"--define",
"ifcbasename={}".format(os.path.splitext(ifc_filename)[0]),
]
)
behave_args.extend([
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# next two lines are one arg
"--outfile",
os.path.join(report_path, "report.json"),
# next two lines are one arg
"--define",
"ifcbasename={}".format(os.path.splitext(ifc_filename)[0]),
# next two lines are one arg
"--define",
"localedir={}".format(locale_path)
])
print(behave_args)
# run tests
from behave.__main__ import main as behave_main
behave_main(behave_args)
print("All tests are finished.")
@@ -249,7 +290,10 @@ def run_intmp_tests(args={}):
def run_all(the_features_path, the_ifcfile):
# run bimtester
runpath = run_intmp_tests({"features": the_features_path, "ifcfile": the_ifcfile})
runpath = run_intmp_tests({
"features": the_features_path,
"ifcfile": the_ifcfile
})
print(runpath)
# check if it worked out well
@@ -263,12 +307,17 @@ def run_all(the_features_path, the_ifcfile):
# create html report and open in webbrowser
from .reports import generate_report
generate_report(runpath)
# get the feature files
feature_files = os.listdir(os.path.join(the_features_path, "features"))
feature_files = os.listdir(
os.path.join(the_features_path, "features")
)
# print(feature_files)
for ff in feature_files:
webbrowser.open(os.path.join(runpath, "report", ff + ".html"))
webbrowser.open(os.path.join(
runpath,
"report",
ff + ".html"
))
return True
+10 -45
View File
@@ -138,57 +138,22 @@ endif
cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz
cd dist/working && tar -xzvf pytz*
cp -r dist/working/pytz-2019.3/pytz dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz
cd dist/working && tar -xzvf python-dateutil*
cp -r dist/working/python-dateutil-2.8.1/dateutil dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz
cd dist/working && tar -xzvf six*
cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/bb/41/ad9ce53bb978b68af8ae415293cafc89b165b8ad55a593725299dca76729/xmlschema-1.1.1.tar.gz
cd dist/working && tar -xzvf xmlschema*
cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz
cd dist/working && tar -xzvf elementpath*
cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides bcfplugin functionality
mkdir dist/working
cd dist/working && wget https://github.com/podestplatz/bcf/archive/master.zip
cd dist/working && unzip master*
cp -r dist/working/bcf-master/bcfplugin dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcfplugin
mkdir dist/working
cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/project.xsd
cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Extension%20Schemas/extensions.xsd
cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/markup.xsd
cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/version.xsd
cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/visinfo.xsd
mkdir dist/blenderbim/libs/site/packages/bcfplugin/schemas/
cp -r dist/working/*.xsd dist/blenderbim/libs/site/packages/bcfplugin/schemas/
# Provides bcf functionality
mkdir dist/blenderbim/libs/site/packages/bcf
mkdir dist/blenderbim/libs/site/packages/bcf/xsd
cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/bcfxml.py
cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/data.py
cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/markup.xsd
cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/project.xsd
cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/version.xsd
cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/visinfo.xsd
rm -rf dist/working
# Required by IFCCSV and ifcopenshell.util.selector
@@ -0,0 +1,2 @@
# addon writes tmp stuff directly to its dir
/data/
@@ -25,15 +25,15 @@ if bpy is not None:
operator.UnassignClass,
operator.SelectClass,
operator.SelectType,
operator.SelectBcfFile,
operator.GetBcfTopics,
operator.NewBcfProject,
operator.LoadBcfProject,
operator.LoadBcfTopics,
operator.SaveBcfProject,
operator.AddBcfTopic,
operator.ViewBcfTopic,
operator.ActivateBcfViewpoint,
operator.OpenBcfFileReference,
operator.OpenUri,
operator.OpenBcfReferenceLink,
operator.OpenBcfBimSnippetSchema,
operator.OpenBcfBimSnippetReference,
operator.OpenBcfDocumentReference,
operator.SelectFeaturesDir,
operator.SelectDiffJsonFile,
operator.SelectDiffNewFile,
@@ -258,12 +258,9 @@ if bpy is not None:
prop.Schedule,
prop.DrawingStyle,
prop.Sheet,
prop.BcfBimSnippet,
prop.BcfDocumentReference,
prop.BcfTopic,
prop.BcfTopicLabel,
prop.BcfTopicFile,
prop.BcfTopicLink,
prop.BcfTopicDocumentReference,
prop.BcfTopicRelatedTopic,
prop.Subcontext,
prop.PresentationLayer,
prop.BIMProperties,
@@ -1,4 +0,0 @@
class BcfStore:
topics = []
viewpoints = []
comments = []
@@ -0,0 +1,11 @@
import bcf
import bcf.bcfxml
class BcfStore:
bcfxml = None
@staticmethod
def get_bcfxml():
if not BcfStore.bcfxml:
BcfStore.bcfxml = bcf.bcfxml.BcfXml()
return BcfStore.bcfxml
+13 -6
View File
@@ -482,13 +482,20 @@ class IfcCutter:
import bpy
multiprocessing.set_executable(bpy.app.binary_path_python)
with multiprocessing.Pool(9) as p:
results = p.map(do_cut, process_data)
for result in results:
polygons = [p for p in result if p["points"]]
if bpy.app.version > (2, 90, 0) and os.name == 'nt':
# See bug #1148
for data in process_data:
results = do_cut(data)
polygons = [r for r in results if r["points"]]
self.cut_polygons.extend(polygons)
else:
multiprocessing.set_executable(bpy.app.binary_path_python)
with multiprocessing.Pool(9) as p:
results = p.map(do_cut, process_data)
for result in results:
polygons = [p for p in result if p["points"]]
self.cut_polygons.extend(polygons)
def get_polygon_metadata(self, polygon, position):
polygon["metadata"] = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), position)}
@@ -0,0 +1,241 @@
"""Viewport decorations"""
import math
from functools import reduce
from itertools import chain
from bpy.types import SpaceView3D
from mathutils import Vector
import bpy
import blf
from bpy_extras.view3d_utils import location_3d_to_region_2d
import gpu
import bgl
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
from gpu_extras.batch import batch_for_shader
class ViewDecorator(object):
# class var for single handler
installed = None
@classmethod
def install(cls, *args, **kwargs):
handler = cls(*args, **kwargs)
cls.installed = SpaceView3D.draw_handler_add(handler, (), 'WINDOW', 'POST_PIXEL')
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, 'WINDOW')
except ValueError:
pass
cls.installed = None
class DimensionDecorator(ViewDecorator):
"""Decorates dimension curves
- outlines each segment with an arrow
- puts metric text next to each segment
"""
VERT_GLSL = """
uniform mat4 viewMatrix;
in vec3 pos;
out vec4 gl_Position;
void main() {
gl_Position = viewMatrix * vec4(pos, 1.0);
}
"""
GEOM_GLSL = """
layout(lines) in;
layout(line_strip, max_vertices=10) out;
uniform float angle;
uniform float length;
uniform float aspect;
void main() {
/** generates arrows from lines */
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
float c = cos(angle), s = sin(angle);
mat4 rot_a = mat4( c, -s, 0, 0,
+s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 rot_b = mat4( c, +s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
// converting to and from square-space coordinates to calculate arrows
mat4 clip2square = mat4(aspect, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
mat4 square2clip = mat4(1/aspect, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
vec4 dir = normalize((p1 - p0) * clip2square) * length;
vec4 head = dir * square2clip;
vec4 arr_a = dir * rot_a * square2clip;
vec4 arr_b = dir * rot_b * square2clip;
gl_Position = p0 + head;
EmitVertex();
gl_Position = p1 - head;
EmitVertex();
EndPrimitive();
gl_Position = p0;
EmitVertex();
gl_Position = p0 + arr_a;
EmitVertex();
gl_Position = p0 + arr_b;
EmitVertex();
gl_Position = p0;
EmitVertex();
EndPrimitive();
gl_Position = p1;
EmitVertex();
gl_Position = p1 - arr_b;
EmitVertex();
gl_Position = p1 - arr_a;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
/*
gl_Position = vec4(0, 0, 0, 1) * square2clip;
EmitVertex();
gl_Position = vec4(0.25, 0.25, 0, 1) * square2clip;
EmitVertex();
EndPrimitive();
*/
}
"""
FRAG_GLSL = """
uniform vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
"""
def __init__(self, props, context):
self.context = context
self.props = props
self.font_id = 0 # TODO: take font from styles
self.dpi = context.preferences.system.dpi
self.shader = self.create_shader()
@classmethod
def create_shader(cls):
return GPUShader(vertexcode=cls.VERT_GLSL, fragcode=cls.FRAG_GLSL, geocode=cls.GEOM_GLSL)
def __call__(self):
# get active drawing, if any
if self.props.active_drawing_index is None or len(self.props.drawings) == 0:
return
drawing = self.props.drawings[self.props.active_drawing_index]
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Dimension"))
self.draw_arrows(segments)
for segm in segments:
self.draw_label(segm, f"{segm[2]:.2f}")
segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Equal"))
self.draw_arrows(segments)
for segm in segments:
self.draw_label(segm, "EQ")
def get_curves(self, collection, basename):
return list(filter(lambda o: basename in o.name, collection.objects))
def get_segments(self, curves):
return list(chain.from_iterable(self.iter_segments(curve) for curve in curves))
def iter_segments(self, curve):
"""Yields each segment converted to world coords
(v0, v1, length)
"""
for spline in curve.data.splines:
spline_points = spline.bezier_points if spline.bezier_points else spline.points
points = [curve.matrix_world @ p.co for p in spline_points]
for i in range(len(points)-1):
p0 = points[i]
p1 = points[i+1]
length = (p1 - p0).length
yield (p0, p1, length)
def draw_label(self, segm, text):
"""Draw text of segment length
aligned and centered at segment middle
"""
p0, p1, _ = segm
# convert to view coords
region = self.context.region
region3d = self.context.region_data
p0 = location_3d_to_region_2d(region, region3d, p0)
p1 = location_3d_to_region_2d(region, region3d, p1)
proj = p1 - p0
if proj.length < 0.001:
return
ang = -Vector((1, 0)).angle_signed(proj)
cos = math.cos(ang)
sin = math.sin(ang)
# midpoint
pos = p0 + (p1 - p0) * .5
# TODO: take font size from styles
blf.size(self.font_id, 16, self.dpi)
w, h = blf.dimensions(self.font_id, text)
# align centered
pos -= Vector((cos, sin)) * w * 0.5
# add padding
# TODO: take padding from styles and adjust to line width
pos += Vector((-sin, cos)) * 4
# TODO: handle overlapping of text with arrows for narrow segments
blf.enable(self.font_id, blf.ROTATION)
blf.position(self.font_id, pos.x, pos.y, 0)
blf.rotation(self.font_id, ang)
blf.draw(self.font_id, text)
blf.disable(self.font_id, blf.ROTATION)
def draw_arrows(self, segments):
def coords(segm):
return [(segm[0].x, segm[0].y, segm[0].z),
(segm[1].x, segm[1].y, segm[1].z)]
points = list(reduce(lambda points, segm: points + coords(segm),
segments, []))
batch = batch_for_shader(self.shader, 'LINES', {'pos': points})
self.shader.bind()
region = self.context.region_data
matrix = region.perspective_matrix
aspect = self.context.region.width / self.context.region.height
self.shader.uniform_float("viewMatrix", matrix)
# TODO: get everything from styles
self.shader.uniform_float('aspect', aspect)
self.shader.uniform_float('color', (1.0, 1.0, 1.0))
self.shader.uniform_float('angle', math.pi / 12)
# brute-force perspective fix
# TODO: move the fix into shader + align heads to view plane
length = 32 / self.context.region.height
if region.is_perspective:
length *= 8
self.shader.uniform_float('length', length)
batch.draw(self.shader)
+154 -127
View File
@@ -20,7 +20,7 @@ from . import svgwriter
from . import sheeter
from . import scheduler
from . import schema
from . import bcf
from . import bcfstore
from . import ifc
from . import annotation
from . import helper
@@ -507,20 +507,124 @@ class RejectElement(bpy.types.Operator):
return {"FINISHED"}
class GetBcfTopics(bpy.types.Operator):
bl_idname = "bim.get_bcf_topics"
bl_label = "Get BCF Topics"
class NewBcfProject(bpy.types.Operator):
bl_idname = "bim.new_bcf_project"
bl_label = "New BCF Project"
def execute(self, context):
import bcfplugin
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bpy.ops.bim.load_bcf_project()
return {"FINISHED"}
bcfplugin.openProject(bpy.context.scene.BCFProperties.bcf_file)
bcf.BcfStore.topics = bcfplugin.getTopics()
class LoadBcfProject(bpy.types.Operator):
bl_idname = "bim.load_bcf_project"
bl_label = "Load BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
if self.filepath:
bcfxml.get_project(self.filepath)
bpy.context.scene.BCFProperties.is_editable = False
bpy.context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
bpy.context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
while len(bpy.context.scene.BCFProperties.topics) > 0:
bpy.context.scene.BCFProperties.topics.remove(0)
for topic in bcf.BcfStore.topics:
for topic in bcfxml.topics.values():
new = bpy.context.scene.BCFProperties.topics.add()
new.name = topic[0]
data_map = {
"name": topic.title,
"guid": topic.guid,
"type": topic.topic_type,
"status": topic.topic_status,
"priority": topic.priority,
"stage": topic.stage,
"creation_date": topic.creation_date,
"creation_author": topic.creation_author,
"modified_date": topic.modified_date,
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
"description": topic.description
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
for reference_link in topic.reference_links:
new2 = new.reference_links.add()
new2.name = reference_link
for label in topic.labels:
new2 = new.labels.add()
new2.name = label
if topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference,
"schema": topic.bim_snippet.reference_schema
}
for key, value in data_map.items():
if value is not None:
setattr(new.bim_snippet, key, value)
for doc in topic.document_references:
new2 = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
"description": doc.description,
"guid": doc.guid,
"is_external": doc.is_external
}
for key, value in data_map.items():
if value is not None:
setattr(new2, key, value)
for related_topic in topic.related_topics:
new2 = new.related_topics.add()
new2.name = related_topic.guid
return {"FINISHED"}
class SaveBcfProject(bpy.types.Operator):
bl_idname = "bim.save_bcf_project"
bl_label = "Save BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.save_project(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class AddBcfTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_topic"
bl_label = "Add BCF Topic"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
new = bpy.context.scene.BCFProperties.topics.add()
new.name = "New Topic"
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -530,8 +634,8 @@ class ViewBcfTopic(bpy.types.Operator):
topic_guid: bpy.props.StringProperty()
def execute(self, context):
for index, topic in enumerate(bcf.BcfStore.topics):
if str(topic[1].xmlId) == self.topic_guid:
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
if topic.guid.lower() == self.topic_guid.lower():
bpy.context.scene.BCFProperties.active_topic_index = index
return {"FINISHED"}
@@ -541,18 +645,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
bl_label = "Activate BCF Viewpoint"
def execute(self, context):
import bcfplugin
topics = bcf.BcfStore.topics
if not topics:
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.guid]
if not topic.viewpoints:
return {"FINISHED"}
topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1]
viewpoints = bcf.BcfStore.viewpoints
if not viewpoints:
return {"FINISHED"}
viewpoint_reference = viewpoints[int(bpy.context.scene.BCFProperties.viewpoints)][1]
viewpoint = viewpoint_reference.viewpoint
viewpoint_guid = blender_topic.viewpoints
viewpoint = topic.viewpoints[viewpoint_guid]
obj = bpy.data.objects.get("Viewpoint")
if not obj:
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
@@ -563,13 +664,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
cam_height = bpy.context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
if viewpoint_reference.snapshot:
if viewpoint.snapshot:
obj.data.show_background_images = True
while len(obj.data.background_images) > 0:
obj.data.background_images.remove(obj.data.background_images[0])
background = obj.data.background_images.new()
background.image = bpy.data.images.load(
os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), viewpoint_reference.snapshot.uri)
os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)
)
src_width = background.image.size[0]
src_height = background.image.size[1]
@@ -583,18 +684,18 @@ class ActivateBcfViewpoint(bpy.types.Operator):
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
if viewpoint.oCamera:
camera = viewpoint.oCamera
if viewpoint.orthogonal_camera:
camera = viewpoint.orthogonal_camera
obj.data.type = "ORTHO"
obj.data.ortho_scale = viewpoint.oCamera.viewWorldScale
elif viewpoint.pCamera:
camera = viewpoint.pCamera
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
elif viewpoint.perspective_camera:
camera = viewpoint.perspective_camera
obj.data.type = "PERSP"
if cam_aspect >= 1:
obj.data.angle = radians(camera.fieldOfView)
obj.data.angle = radians(camera.field_of_view)
else:
# https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov
obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.fieldOfView) / 2)))
obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)))
self.set_viewpoint_components(viewpoint)
@@ -605,37 +706,37 @@ class ActivateBcfViewpoint(bpy.types.Operator):
self.draw_lines(viewpoint)
self.delete_clipping_planes()
if viewpoint.clippingPlanes:
if viewpoint.clipping_planes:
self.create_clipping_planes(viewpoint)
self.delete_bitmaps()
if viewpoint.bitmaps:
self.create_bitmaps(viewpoint)
self.create_bitmaps(bcfxml, viewpoint, topic)
z_axis = Vector((-camera.direction.x, -camera.direction.y, -camera.direction.z)).normalized()
y_axis = Vector((camera.upVector.x, camera.upVector.y, camera.upVector.z)).normalized()
z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized()
y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized()
x_axis = y_axis.cross(z_axis).normalized()
rotation = Matrix((x_axis, y_axis, z_axis))
rotation.invert()
location = Vector((camera.viewPoint.x, camera.viewPoint.y, camera.viewPoint.z))
location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z))
obj.matrix_world = rotation.to_4x4()
obj.location = location
return {"FINISHED"}
def set_viewpoint_components(self, viewpoint):
selected_global_ids = [s.ifcId for s in viewpoint.components.selection]
exception_global_ids = [v.ifcId for v in viewpoint.components.visibilityExceptions]
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
global_id_colours = {}
for colouring in viewpoint.components.colouring:
for component in colouring.components:
global_id_colours.setdefault(component.ifcId, colouring.colour)
for coloring in viewpoint.components.coloring:
for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color)
for obj in bpy.data.objects:
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
continue
global_id = global_id.string_value
is_visible = viewpoint.components.visibilityDefault
is_visible = viewpoint.components.visibility.default_visibility
if global_id in exception_global_ids:
is_visible = not is_visible
if not is_visible:
@@ -666,12 +767,12 @@ class ActivateBcfViewpoint(bpy.types.Operator):
stroke.points.add(len(viewpoint.lines) * 2)
coords = []
for l in viewpoint.lines:
coords.extend([l.start.x, l.start.y, l.start.z, l.end.x, l.end.y, l.end.z])
coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z])
stroke.points.foreach_set("co", coords)
def create_clipping_planes(self, viewpoint):
n = 0
for plane in viewpoint.clippingPlanes:
for plane in viewpoint.clipping_planes:
bpy.ops.bim.add_section_plane()
if n == 0:
obj = bpy.data.objects["Section"]
@@ -700,18 +801,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
for bitmap in collection.objects:
bpy.data.objects.remove(bitmap)
def create_bitmaps(self, viewpoint):
import bcfplugin
topics = bcf.BcfStore.topics
topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1]
def create_bitmaps(self, bcfxml, viewpoint, topic):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
for bitmap in viewpoint.bitmaps:
obj = bpy.data.objects.new("Bitmap", None)
obj.empty_display_type = "IMAGE"
image = bpy.data.images.load(os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), bitmap.reference))
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
src_width = image.size[0]
src_height = image.size[1]
if src_height > src_width:
@@ -719,7 +816,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else:
obj.empty_display_size = bitmap.height * (src_width / src_height)
obj.data = image
y = Vector((bitmap.upVector.x, bitmap.upVector.y, bitmap.upVector.z))
y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z))
z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z))
x = y.cross(z)
obj.matrix_world = Matrix(
@@ -735,22 +832,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
class OpenBcfFileReference(bpy.types.Operator):
bl_idname = "bim.open_bcf_file_reference"
bl_label = "Open BCF File Reference"
data: bpy.props.StringProperty()
class OpenUri(bpy.types.Operator):
bl_idname = "bim.open_uri"
bl_label = "Open URI"
uri: bpy.props.StringProperty()
def execute(self, context):
if "/" not in self.data:
webbrowser.open(bpy.context.scene.BCFProperties.topic_files[int(self.data)].reference)
return {"FINISHED"}
import bcfplugin
topic_guid, index = self.data.split("/")
path = os.path.join(bcfplugin.util.getBcfDir(), topic_guid)
# bpy.context.scene.BCFProperties.topic_files[int(index)].reference)
# TODO - maybe allow immediate importing?
webbrowser.open(path)
webbrowser.open(self.uri)
return {"FINISHED"}
@@ -764,53 +852,6 @@ class OpenBcfReferenceLink(bpy.types.Operator):
return {"FINISHED"}
class OpenBcfBimSnippetSchema(bpy.types.Operator):
bl_idname = "bim.open_bcf_bim_snippet_schema"
bl_label = "Open BCF BIM Snippet Schema"
def execute(self, context):
webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_schema)
return {"FINISHED"}
class OpenBcfBimSnippetReference(bpy.types.Operator):
bl_idname = "bim.open_bcf_bim_snippet_reference"
bl_label = "Open BCF BIM Snippet Reference"
topic_guid: bpy.props.StringProperty()
def execute(self, context):
import bcfplugin
if bpy.context.scene.BCFProperties.topic_snippet_is_external:
webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_reference)
return {"FINISHED"}
webbrowser.open(
"file://"
+ os.path.join(
bcfplugin.util.getBcfDir(), self.topic_guid, bpy.context.scene.BCFProperties.topic_snippet_reference
)
)
return {"FINISHED"}
class OpenBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.open_bcf_document_reference"
bl_label = "Open BCF Document Reference"
data: bpy.props.StringProperty()
def execute(self, context):
import bcfplugin
topic_guid, index = self.data.split("/")
doc = bpy.context.scene.BCFProperties.topic_document_references[int(index)]
uri = doc.name
if doc.is_external:
webbrowser.open(uri)
return {"FINISHED"}
webbrowser.open("file://" + os.path.join(bcfplugin.util.getBcfDir(), topic_guid, uri))
return {"FINISHED"}
class SelectAudited(bpy.types.Operator):
bl_idname = "bim.select_audited"
bl_label = "Select Audited"
@@ -1963,7 +2004,7 @@ class SmartClashGroup(bpy.types.Operator):
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add()
new_group.number = smart_group
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
@@ -1997,7 +2038,7 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add()
new_group.number = int(smart_group)
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
new_global_id = new_group.global_ids.add()
@@ -2030,20 +2071,6 @@ class SelectSmartGroup(bpy.types.Operator):
return {"FINISHED"}
class SelectBcfFile(bpy.types.Operator):
bl_idname = "bim.select_bcf_file"
bl_label = "Select BCF File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BCFProperties.bcf_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectFeaturesDir(bpy.types.Operator):
bl_idname = "bim.select_features_dir"
bl_label = "Select Features Directory"
+59 -192
View File
@@ -5,9 +5,10 @@ import ifcopenshell.util.pset
from pathlib import Path
from . import export_ifc
from . import schema
from . import bcf
from . import bcfstore
from . import ifc
from . import annotation
from . import decoration
import bpy
from bpy.types import PropertyGroup
from bpy.app.handlers import persistent
@@ -51,7 +52,7 @@ persons_enum = []
organisations_enum = []
sheets_enum = []
vector_styles_enum = []
bcfviewpoints_enum = []
bcfviewpoints_enum = None
@persistent
@@ -357,6 +358,14 @@ def refreshTitleblocks(self, context):
getTitleblocks(self, context)
def toggleDecorations(self, context):
toggle = self.should_draw_decorations
if toggle:
decoration.DimensionDecorator.install(self, context)
else:
decoration.DimensionDecorator.uninstall()
def getScenarios(self, context):
global scenarios_enum
if len(scenarios_enum) < 1:
@@ -668,6 +677,7 @@ class DocProperties(PropertyGroup):
active_sheet_index: IntProperty(name="Active Sheet Index")
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
class BIMCameraProperties(PropertyGroup):
@@ -804,7 +814,7 @@ class PresentationLayer(PropertyGroup):
layer_blocked: BoolProperty(name="LayerBlocked", default=False)
class SmartClashGroup(PropertyGroup):
number: IntProperty(name="Number")
number: StringProperty(name="Number")
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
@@ -900,187 +910,64 @@ class Constraint(PropertyGroup):
user_defined_qualifier: StringProperty(name="Custom Qualifier")
class BcfTopic(PropertyGroup):
name: StringProperty(name="Name")
def getBcfViewpoints(self, context):
global bcfviewpoints_enum
if bcfviewpoints_enum is None:
bcfviewpoints_enum = []
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
viewpoints = bcfxml.get_viewpoints(topic.guid)
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
return bcfviewpoints_enum
class BcfTopicLabel(PropertyGroup):
name: StringProperty(name="Name")
class BcfTopicLink(PropertyGroup):
name: StringProperty(name="Name")
class BcfTopicFile(PropertyGroup):
name: StringProperty(name="Name")
class BcfBimSnippet(PropertyGroup):
schema: StringProperty(name="Schema")
reference: StringProperty(name="Reference")
date: StringProperty(name="Date")
type: StringProperty(name="Type")
is_external: BoolProperty(name="Is External")
ifc_project: StringProperty(name="IFC Project")
ifc_spatial: StringProperty(name="IFC Spatial")
class BcfTopicDocumentReference(PropertyGroup):
name: StringProperty(name="Reference")
class BcfDocumentReference(PropertyGroup):
reference: StringProperty(name="Reference")
description: StringProperty(name="Description")
guid: StringProperty(name="GUID")
is_external: BoolProperty(name="Is External")
class BcfTopicRelatedTopic(PropertyGroup):
class BcfTopic(PropertyGroup):
name: StringProperty(name="Name")
guid: StringProperty(name="GUID")
guid: StringProperty(default="", name="GUID")
type: StringProperty(default="", name="Type")
status: StringProperty(default="", name="Status")
priority: StringProperty(default="", name="Priority")
stage: StringProperty(default="", name="Stage")
creation_date: StringProperty(default="", name="Date")
creation_author: StringProperty(default="", name="Author")
modified_date: StringProperty(default="", name="Modified Date")
modified_author: StringProperty(default="", name="Modified By")
assigned_to: StringProperty(default="", name="Assigned To")
due_date: StringProperty(default="", name="Due Date")
description: StringProperty(default="", name="Description")
viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
files: CollectionProperty(name="Files", type=StrProperty)
reference_links: CollectionProperty(name="Reference Links", type=StrProperty)
labels: CollectionProperty(name="Labels", type=StrProperty)
bim_snippet: PointerProperty(type=BcfBimSnippet)
document_references: CollectionProperty(name="Document References", type=BcfDocumentReference)
related_topics: CollectionProperty(name="Related Topics", type=StrProperty)
def refreshBcfTopic(self, context):
RefreshBcfTopic.refresh(context)
class RefreshBcfTopic:
props: None
topic: None
@classmethod
def refresh(cls, context):
global bcfviewpoints_enum
cls.props = bpy.context.scene.BCFProperties
cls.topic = bcf.BcfStore.topics[cls.props.active_topic_index][1]
cls.load_topic_metadata()
cls.load_topic_labels()
cls.load_topic_files()
cls.load_topic_links()
cls.load_snippet()
cls.load_document_references()
cls.load_related_topics()
cls.load_viewpoints()
cls.load_comments()
@classmethod
def load_topic_metadata(cls):
cls.props.topic_guid = str(cls.topic.xmlId)
cls.props.topic_type = cls.topic.type
cls.props.topic_status = cls.topic.status
cls.props.topic_priority = cls.topic.priority
cls.props.topic_stage = cls.topic.stage
if cls.topic.date:
cls.props.topic_creation_date = cls.topic.date.strftime("%a %Y-%m-%d %H:%S")
else:
cls.props.topic_creation_date = ""
cls.props.topic_creation_author = cls.topic.author
if cls.topic.modDate:
cls.props.topic_modified_date = cls.topic.modDate.strftime("%a %Y-%m-%d %H:%S")
else:
cls.props.topic_modified_date = ""
cls.props.topic_modified_author = cls.topic.modAuthor
cls.props.topic_assigned_to = cls.topic.assignee
if cls.topic.dueDate:
cls.props.topic_due_date = cls.topic.dueDate.strftime("%a %Y-%m-%d %H:%S")
else:
cls.props.topic_due_date = ""
cls.props.topic_description = cls.topic.description
@classmethod
def load_topic_labels(cls):
while len(cls.props.topic_labels) > 0:
cls.props.topic_labels.remove(0)
for label in cls.topic.labels:
new = cls.props.topic_labels.add()
new.name = label.value
@classmethod
def load_topic_files(cls):
import bcfplugin
while len(cls.props.topic_files) > 0:
cls.props.topic_files.remove(0)
files = bcfplugin.getRelevantIfcFiles(cls.topic)
for f in files:
new = cls.props.topic_files.add()
new.name = f.filename
new.date = f.time.strftime("%a %Y-%m-%d %H:%S")
new.reference = f.reference.uri
new.ifc_project = f.ifcProjectId
new.ifc_spatial = f.ifcSpatialStructureElement
new.is_external = f.external
@classmethod
def load_topic_links(cls):
while len(cls.props.topic_links) > 0:
cls.props.topic_links.remove(0)
for link in cls.topic.referenceLinks:
new = cls.props.topic_links.add()
new.name = link.value
@classmethod
def load_snippet(cls):
cls.props.topic_has_snippet = bool(cls.topic.bimSnippet)
if cls.topic.bimSnippet:
cls.props.topic_snippet_reference = cls.topic.bimSnippet.reference.uri
if cls.topic.bimSnippet.schema.uri:
cls.props.topic_snippet_schema = cls.topic.bimSnippet.schema.uri
cls.props.topic_snippet_type = cls.topic.bimSnippet.type
if cls.topic.bimSnippet.external:
cls.props.topic_snippet_is_external = cls.topic.bimSnippet.external
else:
cls.props.topic_snippet_is_external = False
@classmethod
def load_document_references(cls):
while len(cls.props.topic_document_references) > 0:
cls.props.topic_document_references.remove(0)
for doc in cls.topic.docRefs:
new = cls.props.topic_document_references.add()
new.name = doc.reference.uri
new.description = doc.description
new.guid = str(doc.guid)
new.is_external = doc.external
@classmethod
def load_related_topics(cls):
import bcfplugin
while len(cls.props.topic_related_topics) > 0:
cls.props.topic_related_topics.remove(0)
for t in cls.topic.relatedTopics:
new = cls.props.topic_related_topics.add()
new.name = bcfplugin.getTopicFromUUID(t.value).title
new.guid = str(t.value)
@classmethod
def load_viewpoints(cls):
import bcfplugin
bcfviewpoints_enum.clear()
bcf.BcfStore.viewpoints = bcfplugin.getViewpoints(cls.topic, realViewpoint=False)
for i, viewpoint in enumerate(bcf.BcfStore.viewpoints):
bcfviewpoints_enum.append((str(i), "View {}".format(i + 1), ""))
@classmethod
def load_comments(cls):
import bcfplugin
bcf.BcfStore.comments = bcfplugin.getComments(cls.topic)
comments = bpy.data.texts.get("BCF Comments")
if comments:
comments.clear()
else:
comments = bpy.data.texts.new("BCF Comments")
for i, comment in enumerate(bcf.BcfStore.comments):
comments.write("# Comment {} - {}\n".format(i + 1, comment[1].xmlId))
comments.write("# From: {} on {}\n".format(comment[1].author, comment[1].date))
if comment[1].modDate:
comments.write("# Modified by {} on {}\n".format(comment[1].modAuthor, comment[1].modDate))
comments.write(comment[1].comment)
comments.write("\n\n-----\n\n")
def getBcfViewpoints(self, context):
global bcfviewpoints_enum
return bcfviewpoints_enum
bcfviewpoints_enum = None
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
header = bcfxml.get_header(topic.guid)
getBcfViewpoints(self, context)
class PropertySetTemplate(PropertyGroup):
@@ -1589,32 +1476,12 @@ class BIMProperties(PropertyGroup):
class BCFProperties(PropertyGroup):
bcf_file: StringProperty(default="", name="BCF File")
is_editable: BoolProperty(name="Is Editable", default=False)
is_loaded: BoolProperty(name="Is Loaded", default=False)
name: StringProperty(default="", name="Project Name")
author: StringProperty(default="john@doe.com", name="Author Email")
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
topic_guid: StringProperty(default="", name="Topic GUID")
topic_type: StringProperty(default="", name="Topic Type")
topic_status: StringProperty(default="", name="Topic Status")
topic_priority: StringProperty(default="", name="Topic Priority")
topic_stage: StringProperty(default="", name="Topic Stage")
topic_creation_date: StringProperty(default="", name="Topic Date")
topic_creation_author: StringProperty(default="", name="Topic Author")
topic_modified_date: StringProperty(default="", name="Topic Modified Date")
topic_modified_author: StringProperty(default="", name="Topic Modified By")
topic_assigned_to: StringProperty(default="", name="Topic Assigned To")
topic_due_date: StringProperty(default="", name="Topic Due Date")
topic_description: StringProperty(default="", name="Topic Description")
topic_labels: CollectionProperty(name="BCF Topic Labels", type=BcfTopicLabel)
topic_files: CollectionProperty(name="BCF Topic Files", type=BcfTopicFile)
topic_links: CollectionProperty(name="BCF Topic Links", type=BcfTopicLink)
topic_has_snippet: BoolProperty(name="BCF Topic Has Snippet", default=False)
topic_snippet_reference: StringProperty(name="BIM Snippet Reference")
topic_snippet_schema: StringProperty(name="BIM Snippet Schema")
topic_snippet_type: StringProperty(name="BIM Snippet Type")
topic_snippet_is_external: BoolProperty(name="Is BIM Snippet External")
topic_document_references: CollectionProperty(name="BCF Topic Document References", type=BcfTopicDocumentReference)
topic_related_topics: CollectionProperty(name="BCF Topic Related Topics", type=BcfTopicRelatedTopic)
class MapConversion(PropertyGroup):
+103 -91
View File
@@ -1,4 +1,6 @@
import os
import bpy
from . import bcfstore
from bpy.types import Panel
from bpy.props import StringProperty
@@ -1661,108 +1663,115 @@ class BIM_PT_bcf(Panel):
props = bpy.context.scene.BCFProperties
row = layout.row(align=True)
row.prop(props, "bcf_file")
row.operator("bim.select_bcf_file", icon="FILE_FOLDER", text="")
row.operator("bim.new_bcf_project", text="New Project")
row.operator("bim.load_bcf_project", text="Load Project")
if not props.is_loaded:
return
row.operator("bim.save_bcf_project", text="Save Project")
row = layout.row()
row.operator("bim.get_bcf_topics")
row.prop(props, "name")
row = layout.row()
row.prop(props, "author")
props = bpy.context.scene.BCFProperties
layout.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
row = layout.row()
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
col = row.column(align=True)
col.operator("bim.add_bcf_topic", icon="ADD", text="")
row = layout.row()
row.prop(props, "topic_description", text="")
row = layout.row()
row.prop(props, "viewpoints")
row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="")
row = layout.row()
row.prop(props, "topic_type", text="Type")
row = layout.row()
row.prop(props, "topic_status", text="Status")
row = layout.row()
row.prop(props, "topic_priority", text="Priority")
row = layout.row()
row.prop(props, "topic_stage", text="Stage")
row = layout.row()
row.prop(props, "topic_creation_date", text="Date")
row = layout.row()
row.prop(props, "topic_creation_author", text="Author")
row = layout.row()
row.prop(props, "topic_modified_date", text="Modified On")
row = layout.row()
row.prop(props, "topic_modified_author", text="Modified By")
row = layout.row()
row.prop(props, "topic_assigned_to", text="Assigned To")
row = layout.row()
row.prop(props, "topic_due_date", text="Due Date")
layout.label(text="Header Files:")
for index, f in enumerate(props.topic_files):
if props.active_topic_index < len(props.topics):
topic = props.topics[props.active_topic_index]
row = layout.row()
row.prop(f, "name", text="File {} Name".format(index + 1))
row.prop(topic, "description", text="")
row = layout.row()
row.prop(f, "reference", text="File {} URI".format(index + 1))
if f.is_external:
row.operator("bim.open_bcf_file_reference", icon="URL", text="").data = index
else:
row.operator("bim.open_bcf_file_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format(
props.topic_guid, index
)
row = layout.row()
row.prop(f, "date", text="File {} Date".format(index + 1))
row = layout.row()
row.prop(f, "ifc_project", text="File {} Project".format(index + 1))
row = layout.row()
row.prop(f, "ifc_spatial", text="File {} Spatial".format(index + 1))
row.prop(topic, "viewpoints")
row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="")
layout.label(text="Reference Links:")
for index, label in enumerate(props.topic_links):
row = layout.row()
row.prop(label, "name", text="Link {}".format(index + 1))
row.operator("bim.open_bcf_reference_link", icon="URL", text="").index = index
col = layout.column(align=True)
col.prop(topic, "type")
col.prop(topic, "status")
col.prop(topic, "priority")
col.prop(topic, "stage")
col.prop(topic, "assigned_to")
col.prop(topic, "due_date")
layout.label(text="Labels:")
for index, label in enumerate(props.topic_labels):
row = layout.row(align=True)
row.prop(label, "name", text="")
col = layout.column(align=True)
col.enabled = False
col.prop(topic, "creation_date")
col.prop(topic, "creation_author")
col.prop(topic, "modified_date")
col.prop(topic, "modified_author")
layout.label(text="BIM Snippet:")
if props.topic_has_snippet:
row = layout.row(align=True)
row.prop(props, "topic_snippet_type")
if props.topic_snippet_schema:
row.operator("bim.open_bcf_bim_snippet_schema", icon="URL", text="")
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcf_topic = bcfxml.topics[topic.guid]
row = layout.row(align=True)
row.prop(props, "topic_snippet_reference")
if props.topic_snippet_is_external:
row.operator("bim.open_bcf_bim_snippet_reference", icon="URL", text="")
else:
row.operator(
"bim.open_bcf_bim_snippet_reference", icon="FILE_FOLDER", text=""
).topic_guid = props.topic_guid
if bcf_topic.header:
layout.label(text="Header Files:")
for index, f in enumerate(bcf_topic.header.files):
box = self.layout.box()
row = box.row(align=True)
row.label(text=f.filename, icon="FILE_BLANK")
if f.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.guid, f.reference)
box.label(text=f.date)
#box.label(text=f.ifc_project)
#box.label(text=f.ifc_spatial_structure_element)
layout.label(text="Document References:")
for index, doc in enumerate(props.topic_document_references):
row = layout.row(align=True)
row.prop(doc, "name", text=f"File {index+1} URI")
if doc.is_external:
row.operator("bim.open_bcf_document_reference", icon="URL", text="").data = "{}/{}".format(
props.topic_guid, index
)
else:
row.operator("bim.open_bcf_document_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format(
props.topic_guid, index
)
row = layout.row(align=True)
row.prop(doc, "description", text=f"File {index+1} Description:")
if topic.reference_links:
layout.label(text="Reference Links:")
for index, link in enumerate(topic.reference_links):
row = layout.row(align=True)
row.prop(link, "name")
row.operator("bim.open_uri", icon="URL", text="").uri = link.name
layout.label(text="Related Topics:")
for topic in props.topic_related_topics:
row = layout.row(align=True)
row.operator("bim.view_bcf_topic", text=topic.name).topic_guid = topic.guid
if topic.labels:
layout.label(text="Labels:")
for index, label in enumerate(topic.labels):
row = layout.row(align=True)
row.prop(label, "name", text="")
if topic.bim_snippet.schema:
layout.label(text="BIM Snippet:")
row = layout.row(align=True)
row.prop(topic.bim_snippet, "type")
if topic.bim_snippet.schema:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema
row = layout.row(align=True)
row.prop(topic.bim_snippet, "reference")
if topic.bim_snippet.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.guid, topic.bim_snippet.reference)
if topic.document_references:
layout.label(text="Document References:")
for index, doc in enumerate(topic.document_references):
box = self.layout.box()
row = box.row(align=True)
row.prop(doc, "reference")
if doc.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.guid, doc.reference)
row = box.row(align=True)
row.prop(doc, "description")
if topic.related_topics:
layout.label(text="Related Topics:")
for related_topic in topic.related_topics:
row = layout.row(align=True)
row.operator("bim.view_bcf_topic", text=related_topic.name).topic_guid = related_topic.name
class BIM_PT_qa(Panel):
@@ -2352,6 +2361,9 @@ class BIM_PT_annotation_utilities(Panel):
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index")
row = layout.row()
row.prop(props, "should_draw_decorations")
class BIM_PT_qto_utilities(Panel):
bl_idname = "BIM_PT_qto_utilities"
@@ -2396,7 +2408,7 @@ class BIM_PT_clash_manager(Panel):
row = layout.row()
layout.label(text="Select output path for smart-grouped clashes:")
row = layout.row(align=True)
row.prop(props, "smart_grouped_clashes_path", text="")
op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="")
@@ -2409,7 +2421,7 @@ class BIM_PT_clash_manager(Panel):
row = layout.row(align=True)
row.operator("bim.load_smart_groups_for_active_clash_set")
layout.template_list('BIM_UL_smart_groups', '', props, 'smart_clash_groups', props, 'active_smart_group_index')
row = layout.row(align=True)
+20 -1
View File
@@ -283,7 +283,9 @@ class IfcClasher:
continue
clashes = clash_set["clashes"]
if len(clashes) == 0:
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
continue
count_of_input_clashes += len(clashes)
positions = []
@@ -307,7 +309,13 @@ class IfcClasher:
if len(pred) == len(clashes.values()):
i = 0
for clash in clashes.values():
clash["smart_group"] = int(pred[i])
int_prediction = int(pred[i])
if int_prediction == -1:
# ungroup this clash since it's a single clash that we were not able to group.
new_clash_group_number = np.amax(pred).item() + 1 + i
clash["smart_group"] = new_clash_group_number
else:
clash["smart_group"] = int_prediction
i += 1
# Create JSON with smart_groups that contain GlobalIDs
@@ -326,6 +334,17 @@ class IfcClasher:
count_of_smart_groups += len(smart_groups)
output_clash_sets[clash_set["name"]].append(smart_groups)
# Rename the clash groups to something more sensible
for clash_set, smart_groups in output_clash_sets.items():
clash_set_name = clash_set
# Only select the clashes that correspond to the actively selected IFC Clash Set
i = 1
new_smart_group_name = ""
for smart_group, global_id_pairs in list(smart_groups[0].items()):
new_smart_group_name = f"{clash_set_name} - {i}"
smart_groups[0][new_smart_group_name] = smart_groups[0].pop(smart_group)
i += 1
count_of_final_clash_sets = len(output_clash_sets)
print(f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets")
@@ -204,7 +204,7 @@ class Selector:
value = filter_rule.children[2].children[0][1:-1]
for element in elements:
element_value = self.get_element_value(element, key)
if not element_value:
if element_value is None:
continue
if not comparison or self.filter_element(element, element_value, comparison, value):
results.append(element)
+1 -1
View File
@@ -1304,7 +1304,7 @@ void IfcEntityInstanceData::setArgument(unsigned int i, Argument* a, IfcUtil::Ar
//
#ifdef USE_MMAP
IfcFile::IfcFile(const std::string& fn, bool mmap) {
return IfcFile::Init(new IfcSpfStream(fn, mmap));
initialize_(new IfcSpfStream(fn, mmap));
}
#else
IfcFile::IfcFile(const std::string& fn) {