Skip to content
This repository was archived by the owner on Nov 24, 2024. It is now read-only.

Commit 5ad6abe

Browse files
committed
Add simple BCF library to supersede bcfplugin, with bcfxml read capability
1 parent d00da09 commit 5ad6abe

7 files changed

Lines changed: 847 additions & 0 deletions

File tree

src/bcf/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# bcf
2+
3+
A simple Python implementation of BCF. The data model is described in `data.py`.
4+
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
5+
is available via `bcfapi.py`.
6+
7+
Currently supports BCF version 2.1.

src/bcf/bcf/bcfxml.py

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import os
2+
import tempfile
3+
import shutil
4+
import zipfile
5+
import logging
6+
import bcf.data
7+
from xmlschema import XMLSchema
8+
from contextlib import contextmanager
9+
10+
11+
cwd = os.path.dirname(os.path.realpath(__file__))
12+
13+
14+
@contextmanager
15+
def cd(newdir):
16+
prevdir = os.getcwd()
17+
os.chdir(os.path.expanduser(newdir))
18+
try:
19+
yield
20+
finally:
21+
os.chdir(prevdir)
22+
23+
24+
class BcfXml:
25+
def __init__(self):
26+
self.filepath = None
27+
self.logger = logging.getLogger("bcfxml")
28+
self.project = bcf.data.Project()
29+
self.topics = {}
30+
31+
def get_project(self, filepath=None):
32+
if not filepath:
33+
return self.project
34+
zip_file = zipfile.ZipFile(filepath)
35+
self.filepath = tempfile.mkdtemp()
36+
zip_file.extractall(self.filepath)
37+
data = self._read_xml("project.bcfp", "project.xsd")
38+
self.project.project_id = data["Project"]["@ProjectId"]
39+
self.project.name = data["Project"]["Name"]
40+
return self.project
41+
42+
def edit_project(self):
43+
pass
44+
45+
def save_project(self, filepath):
46+
with cd(self.filepath):
47+
zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
48+
for root, dirs, files in os.walk("./"):
49+
for file in files:
50+
zip_file.write(os.path.join(root, file))
51+
zip_file.close()
52+
53+
def get_version(self):
54+
data = self._read_xml("bcf.version", "version.xsd")
55+
return data["@VersionId"]
56+
57+
def get_topics(self):
58+
self.topics = {}
59+
topics = []
60+
subdirs = []
61+
for (dirpath, dirnames, filenames) in os.walk(self.filepath):
62+
subdirs = dirnames
63+
break
64+
for subdir in subdirs:
65+
self.topics[subdir] = self.get_topic(subdir)
66+
return self.topics
67+
68+
def get_topic(self, guid):
69+
if guid in self.topics:
70+
return self.topics[guid]
71+
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
72+
topic = bcf.data.Topic()
73+
self.topics[guid] = topic
74+
75+
mandatory_keys = {
76+
"guid": "@Guid",
77+
"title": "Title",
78+
"creation_date": "CreationDate",
79+
"creation_author": "CreationAuthor",
80+
}
81+
for key, value in mandatory_keys.items():
82+
setattr(topic, key, data["Topic"][value])
83+
84+
optional_keys = {
85+
"priority": "Priority",
86+
"index": "Index",
87+
"labels": "Labels",
88+
"modified_date": "ModifiedDate",
89+
"modified_author": "ModifiedAuthor",
90+
"due_date": "DueDate",
91+
"assigned_to": "AssignedTo",
92+
"stage": "Stage",
93+
"description": "Description",
94+
"topic_status": "TopicStatus",
95+
"topic_type": "TopicType",
96+
}
97+
for key, value in optional_keys.items():
98+
if value in data["Topic"]:
99+
setattr(topic, key, data["Topic"][value])
100+
101+
if "BimSnippet" in data["Topic"]:
102+
bim_snippet = bcf.data.BimSnippet()
103+
keys = {
104+
"snippet_type": "@SnippetType",
105+
"is_external": "@IsExternal",
106+
"reference": "Reference",
107+
"reference_schema": "ReferenceSchema",
108+
}
109+
for key, value in keys.items():
110+
if value in data["Topic"]["BimSnippet"]:
111+
setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
112+
topic.bim_snippet = bim_snippet
113+
114+
if "DocumentReference" in data["Topic"]:
115+
for item in data["Topic"]["DocumentReference"]:
116+
document_reference = bcf.data.DocumentReference()
117+
keys = {
118+
"referenced_document": "ReferencedDocument",
119+
"is_external": "@IsExternal",
120+
"guid": "@Guid",
121+
"description": "Description",
122+
}
123+
for key, value in keys.items():
124+
if value in item:
125+
setattr(document_reference, key, item[value])
126+
topic.document_references.append(document_reference)
127+
128+
if "RelatedTopic" in data["Topic"]:
129+
for item in data["Topic"]["RelatedTopic"]:
130+
related_topic = bcf.data.RelatedTopic()
131+
related_topic.guid = item["@Guid"]
132+
topic.related_topics.append(related_topic)
133+
return topic
134+
135+
def get_header(self, guid):
136+
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
137+
138+
def get_comments(self, guid):
139+
comments = []
140+
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
141+
if "Comment" not in data:
142+
return comments
143+
for item in data["Comment"]:
144+
comment = bcf.data.Comment()
145+
mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
146+
for key, value in mandatory_keys.items():
147+
setattr(comment, key, item[value])
148+
optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
149+
for key, value in optional_keys.items():
150+
if value in item:
151+
setattr(comment, key, item[value])
152+
if "Viewpoint" in item:
153+
viewpoint = bcf.data.Viewpoint()
154+
viewpoint.guid = item["Viewpoint"]["@Guid"]
155+
comment.viewpoint = viewpoint
156+
comments.append(comment)
157+
return comments
158+
159+
def get_viewpoints(self, guid):
160+
viewpoints = []
161+
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
162+
if "Viewpoints" not in data:
163+
return viewpoints
164+
for item in data["Viewpoints"]:
165+
viewpoints.append(self.get_viewpoint(item, guid))
166+
return viewpoints
167+
168+
def get_viewpoint(self, data, topic_guid):
169+
viewpoint = bcf.data.Viewpoint()
170+
viewpoint.guid = data["@Guid"]
171+
optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
172+
for key, value in optional_keys.items():
173+
if value in data:
174+
setattr(viewpoint, key, data[value])
175+
visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
176+
viewpoint.components = self.get_viewpoint_components(visinfo)
177+
viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
178+
viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
179+
viewpoint.lines = self.get_viewpoint_lines(visinfo)
180+
viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
181+
viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
182+
return viewpoint
183+
184+
def get_viewpoint_components(self, visinfo):
185+
if "Components" not in visinfo:
186+
return None
187+
components = bcf.data.Components()
188+
data = visinfo["Components"]
189+
if "ViewSetupHints" in data:
190+
view_setup_hints = bcf.data.ViewSetupHints()
191+
optional_keys = {
192+
"spaces_visible": "@SpacesVisible",
193+
"space_boundaries_visible": "@SpaceBoundariesVisible",
194+
"openings_visible": "@OpeningsVisible",
195+
}
196+
for key, value in optional_keys.items():
197+
if value in data["ViewSetupHints"]:
198+
setattr(view_setup_hints, key, data["ViewSetupHints"][value])
199+
components.view_setup_hints = view_setup_hints
200+
if "Selection" in data and "Component" in data["Selection"]:
201+
for item in data["Selection"]["Component"]:
202+
components.selection.append(self.get_component(item))
203+
if "Visibility" in data:
204+
component_visibility = bcf.data.ComponentVisibility()
205+
if "@DefaultVisibility" in data["Visibility"]:
206+
component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
207+
if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
208+
for item in data["Visibility"]["Exceptions"]["Component"]:
209+
component_visibility.exceptions.append(self.get_component(item))
210+
components.visibility = component_visibility
211+
if "Coloring" in data and "Color" in data["Coloring"]:
212+
for item in data["Coloring"]["Color"]:
213+
color = bcf.data.Color()
214+
color.color = item["@Color"]
215+
for item2 in item["Component"]:
216+
color.components.append(self.get_component(item2))
217+
components.coloring.append(color)
218+
return components
219+
220+
def get_viewpoint_orthogonal_camera(self, visinfo):
221+
if "OrthogonalCamera" not in visinfo:
222+
return None
223+
camera = bcf.data.OrthogonalCamera()
224+
data = visinfo["OrthogonalCamera"]
225+
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
226+
self.set_vector(camera.camera_direction, data["CameraDirection"])
227+
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
228+
camera.view_to_world_scale = data["ViewToWorldScale"]
229+
230+
def get_viewpoint_perspective_camera(self, visinfo):
231+
if "PerspectiveCamera" not in visinfo:
232+
return None
233+
camera = bcf.data.PerspectiveCamera()
234+
data = visinfo["PerspectiveCamera"]
235+
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
236+
self.set_vector(camera.camera_direction, data["CameraDirection"])
237+
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
238+
camera.field_of_view = data["FieldOfView"]
239+
240+
def get_viewpoint_lines(self, visinfo):
241+
if "Lines" not in visinfo:
242+
return []
243+
lines = []
244+
for item in visinfo["Lines"]["Line"]:
245+
line = bcf.data.Line()
246+
self.set_vector(line.start_point, item["StartPoint"])
247+
self.set_vector(line.end_point, item["EndPoint"])
248+
lines.append(line)
249+
return lines
250+
251+
def get_viewpoint_clipping_planes(self, visinfo):
252+
if "ClippingPlanes" not in visinfo:
253+
return []
254+
planes = []
255+
for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
256+
plane = bcf.data.ClippingPlane()
257+
self.set_vector(plane.location, item["Location"])
258+
self.set_vector(plane.direction, item["Direction"])
259+
planes.append(plane)
260+
return planes
261+
262+
def get_viewpoint_bitmaps(self, visinfo):
263+
if "Bitmap" not in visinfo:
264+
return []
265+
bitmaps = []
266+
for item in visinfo["Bitmap"]:
267+
bitmap = bcf.data.Bitmap()
268+
bitmap.reference = item["Reference"]
269+
bitmap.bitmap_type = item["Bitmap"].lower()
270+
self.set_vector(bitmap.location, item["Location"])
271+
self.set_vector(bitmap.normal, item["Normal"])
272+
self.set_vector(bitmap.up, item["Up"])
273+
bitmap.height = item["Height"]
274+
bitmaps.append(bitmap)
275+
return bitmaps
276+
277+
def set_vector(self, to_obj, from_xml):
278+
to_obj.x = from_xml["X"]
279+
to_obj.y = from_xml["Y"]
280+
to_obj.z = from_xml["Z"]
281+
282+
def get_component(self, data):
283+
component = bcf.data.Component()
284+
optional_keys = {
285+
"originating_system": "OriginatingSystem",
286+
"authoring_tool_id": "AuthoringToolId",
287+
"ifc_guid": "@IfcGuid",
288+
}
289+
for key, value in optional_keys.items():
290+
if value in data:
291+
setattr(component, key, data[value])
292+
return component
293+
294+
def close_project(self):
295+
shutil.rmtree(self.filepath)
296+
297+
def _read_xml(self, filename, xsd):
298+
schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
299+
filepath = os.path.join(self.filepath, filename)
300+
(data, errors) = schema.to_dict(filepath, validation="lax")
301+
for error in errors:
302+
self.logger.error(error)
303+
return data
304+
305+
def __del__(self):
306+
self.close_project()

0 commit comments

Comments
 (0)