mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 10:57:49 +00:00
@@ -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)
|
||||
```
|
||||
@@ -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()
|
||||
@@ -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 = []
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Binary file not shown.
@@ -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 {}"
|
||||
Binary file not shown.
@@ -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 {}"
|
||||
Binary file not shown.
@@ -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 ""
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user