Skip to content

Commit 8eae997

Browse files
committed
bcf v3, bonsai bcf v3 - support extracting files #2790
extracting files in general is also improved - now it's possible to extract a file that was just added to bcf in memory and not yet saved to the disk. Update model.py
1 parent fee56fc commit 8eae997

File tree

4 files changed

+102
-63
lines changed

4 files changed

+102
-63
lines changed

src/bcf/bcf/agnostic/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
from typing import Union
44

55
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
6+
DocumentReference = Union[bcf.v2.model.TopicDocumentReference, bcf.v3.model.DocumentReference]
7+
HeaderFile = Union[bcf.v2.model.HeaderFile, bcf.v3.model.File]

src/bcf/bcf/agnostic/topic.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,93 @@
1+
import tempfile
2+
import bcf.v2.bcfxml
3+
import bcf.v2.model
14
import bcf.v2.topic
5+
import bcf.v3.bcfxml
6+
import bcf.v3.model
27
import bcf.v3.topic
3-
from typing import Union
8+
import bcf.agnostic.model as mdl
9+
from pathlib import Path
10+
from typing import Union, Optional
11+
from typing_extensions import assert_never
412

513
TopicHandler = Union[bcf.v2.topic.TopicHandler, bcf.v3.topic.TopicHandler]
14+
15+
16+
def extract_file(
17+
topic: TopicHandler,
18+
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference],
19+
bcfxml: Optional[Union[bcf.v2.bcfxml.BcfXml, bcf.v3.bcfxml.BcfXml]] = None,
20+
outfile: Optional[Path] = None,
21+
) -> Union[Path, str, None]:
22+
"""Extracts an element with a file into a temporary directory
23+
24+
These include header files, bim snippets, document references, and
25+
viewpoint bitmaps. External reference are not downloaded. Instead, the
26+
URI reference is returned.
27+
28+
:param entity: The entity with a file reference to extract
29+
:param outfile: If provided, save the header file to that location.
30+
Otherwise, a temporary directory is created and the filename is
31+
derived from the header's original filename.
32+
:param bcfxml: The BCF XML file to use for resolving document references files.
33+
Required only for BCF v3 document references (in BCF v3 internal documents
34+
are stored at BCF root, not in the topic).
35+
:return: The filepath of the extracted file. It may be a URL if the
36+
header file is external.
37+
"""
38+
if isinstance(entity, mdl.DocumentReference):
39+
if isinstance(entity, bcf.v2.model.TopicDocumentReference):
40+
reference = entity.referenced_document
41+
else:
42+
reference = entity.document_guid
43+
else:
44+
reference = entity.reference
45+
46+
if not reference:
47+
return None
48+
49+
# For v3 document references external documents are detected by empty document_guid.
50+
if not isinstance(entity, bcf.v3.model.DocumentReference) and entity.is_external:
51+
return reference
52+
53+
if isinstance(entity, bcf.v3.model.DocumentReference):
54+
# Extract document reference filename and contents.
55+
if not bcfxml:
56+
raise TypeError("bcfxml is required for BCF v3 document references.")
57+
assert isinstance(bcfxml, bcf.v3.bcfxml.BcfXml)
58+
error_msg = f"BCF XML is missing document with guid '{reference}'."
59+
if not bcfxml.documents:
60+
raise Exception(error_msg)
61+
definition_docs = bcfxml.documents.definition.documents
62+
if not definition_docs:
63+
raise Exception(error_msg)
64+
docs = next((doc for doc in definition_docs.document if doc.guid == reference), None)
65+
if not docs:
66+
raise Exception(error_msg)
67+
filename = docs.filename
68+
bytes_data = bcfxml.documents.documents[filename]
69+
else:
70+
if isinstance(entity, mdl.BimSnippet):
71+
bytes_data = topic.bim_snippet
72+
assert isinstance(bytes_data, bytes)
73+
elif isinstance(entity, mdl.HeaderFile):
74+
bytes_data = topic.reference_files[reference]
75+
elif isinstance(entity, bcf.v2.model.TopicDocumentReference):
76+
assert isinstance(topic, bcf.v2.topic.TopicHandler)
77+
bytes_data = topic.document_references[reference]
78+
else:
79+
assert_never(entity)
80+
81+
# We don't really need it if 'outfile' is None, just keeping type checker happy.
82+
if isinstance(entity, mdl.HeaderFile) and entity.filename:
83+
filename = entity.filename
84+
else:
85+
filename = Path(reference).name
86+
87+
if not outfile:
88+
outfile = Path(tempfile.mkdtemp()) / filename
89+
90+
with open(outfile, "wb") as f:
91+
f.write(bytes_data)
92+
93+
return outfile

src/bcf/bcf/v2/topic.py

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -240,54 +240,6 @@ def _save_document_references(self, destination_zip: ZipFileInterface) -> None:
240240
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
241241
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
242242

243-
def extract_file(
244-
self, entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.TopicDocumentReference], outfile: Optional[Path] = None
245-
) -> Union[Path, str, None]:
246-
"""Extracts an element with a file into a temporary directory
247-
248-
These include header files, bim snippets, document references, and
249-
viewpoint bitmaps. External reference are not downloaded. Instead, the
250-
URI reference is returned.
251-
252-
:param entity: The entity with a file reference to extract
253-
:param outfile: If provided, save the header file to that location.
254-
Otherwise, a temporary directory is created and the filename is
255-
derived from the header's original filename.
256-
:return: The filepath of the extracted file. It may be a URL if the
257-
header file is external.
258-
:rtype: Path
259-
"""
260-
if hasattr(entity, "reference"):
261-
reference = entity.reference
262-
else:
263-
reference = entity.referenced_document
264-
265-
if not reference:
266-
return
267-
268-
if getattr(entity, "is_external", False):
269-
return entity.reference
270-
271-
resolved_reference = self._topic_dir
272-
273-
for part in Path(reference).parts:
274-
if part == "..":
275-
resolved_reference = resolved_reference.parent
276-
else:
277-
resolved_reference = resolved_reference.joinpath(part)
278-
279-
if not outfile:
280-
if getattr(entity, "filename", None):
281-
filename = entity.filename
282-
else:
283-
filename = resolved_reference.name
284-
outfile = Path(tempfile.mkdtemp()) / filename
285-
286-
with open(outfile, "wb") as f:
287-
f.write(resolved_reference.read_bytes())
288-
289-
return outfile
290-
291243
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
292244
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
293245

src/bonsai/bonsai/bim/module/bcf/operator.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,14 +1493,10 @@ def execute(self, context):
14931493
bcfxml = bcfstore.BcfStore.get_bcfxml()
14941494
assert bcfxml
14951495

1496-
if not (version := (bcfxml.version.version_id or "")).startswith("2"):
1497-
self.report({"INFO"}, f"BCF {version} is not yet supported: {self.bl_rna.bl_idname}.")
1498-
return {"FINISHED"}
1499-
15001496
bcf_path = tool.Bcf.get_path()
15011497
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
1502-
entity = topic.header.file[self.index]
1503-
ifc_path = str(topic.extract_file(entity))
1498+
entity = tool.Bcf.get_topic_header_files(topic)[self.index]
1499+
ifc_path = bcf.agnostic.topic.extract_file(topic, entity)
15041500
bpy.ops.bim.load_project(filepath=ifc_path)
15051501
if bcf_path:
15061502
bpy.ops.bim.load_bcf_project(filepath=bcf_path)
@@ -1518,20 +1514,21 @@ def execute(self, context):
15181514
bcfxml = bcfstore.BcfStore.get_bcfxml()
15191515
assert bcfxml
15201516

1521-
if not (version := (bcfxml.version.version_id or "")).startswith("2"):
1522-
self.report({"INFO"}, f"BCF {version} is not yet supported: {self.bl_rna.bl_idname}.")
1523-
return {"FINISHED"}
1524-
15251517
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
15261518

15271519
if self.entity_type == "HEADER_FILE":
1528-
entity = topic.header.file[self.index]
1520+
entity = tool.Bcf.get_topic_header_files(topic)[self.index]
15291521
elif self.entity_type == "BIM_SNIPPET":
1530-
entity = topic.markup.topic.bim_snippet
1522+
entity = topic.topic.bim_snippet
15311523
elif self.entity_type == "DOCUMENT_REFERENCE":
1532-
entity = topic.markup.topic.document_reference[self.index]
1524+
entity = tool.Bcf.get_topic_document_references(topic)[self.index]
1525+
else:
1526+
assert False
15331527

1534-
webbrowser.open(str(topic.extract_file(entity).parent))
1528+
assert entity
1529+
filepath = bcf.agnostic.topic.extract_file(topic, entity, bcfxml)
1530+
assert isinstance(filepath, Path)
1531+
webbrowser.open(str(filepath.parent))
15351532
return {"FINISHED"}
15361533

15371534

0 commit comments

Comments
 (0)