diff --git a/src/bcf/README.md b/src/bcf/README.md index 5aeade120f..a792c4f79f 100644 --- a/src/bcf/README.md +++ b/src/bcf/README.md @@ -27,7 +27,7 @@ with load("/path/to/file.bcf") as bcfxml: # Get a dictionary of topics topics = bcfxml.topics - for topic_handler in bcfxml.topics: + for topic_guid, topic_handler in bcfxml.topics.items(): topic = topic_handler.topic print("Topic guid is", topic.guid) print("Topic title is", topic.title) diff --git a/src/bcf/src/bcf/v2/topic.py b/src/bcf/src/bcf/v2/topic.py index 94c36068b3..806d445bb0 100644 --- a/src/bcf/src/bcf/v2/topic.py +++ b/src/bcf/src/bcf/v2/topic.py @@ -1,7 +1,8 @@ """BCF XML V2 Topic handler.""" -import datetime import uuid import zipfile +import tempfile +import datetime from pathlib import Path from typing import Any, NoReturn, Optional @@ -218,9 +219,56 @@ class TopicHandler: real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part) destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document]) - def add_viewpoint(self, element: entity_instance) -> None: + def extract_file(self, entity, outfile: Optional[Path] = None) -> Path: + """Extracts an element with a file into a temporary directory + + These include header files, bim snippets, document references, and + viewpoint bitmaps. External reference are not downloaded. Instead, the + URI reference is returned. + + :param entity: The entity with a file reference to extract + :type entity: bcf.v2.model.HeaderFile,bcf.v2.model.BimSnippet,bcf.v2.model.TopicDocumentReference + :param outfile: If provided, save the header file to that location. + Otherwise, a temporary directory is created and the filename is + derived from the header's original filename. + :type outfile: pathlib.Path,optional + :return: The filepath of the extracted file. It may be a URL if the + header file is external. + :rtype: Path """ - Add a viewpoint tergeting an IFC element to the topic. + if hasattr(entity, "reference"): + reference = entity.reference + else: + reference = entity.referenced_document + + if not reference: + return + + if getattr(entity, "is_external", False): + return entity.reference + + resolved_reference = self._topic_dir + + for part in Path(reference).parts: + if part == "..": + resolved_reference = resolved_reference.parent + else: + resolved_reference = resolved_reference.joinpath(part) + + if not outfile: + if getattr(entity, "filename", None): + filename = entity.filename + else: + filename = resolved_reference.name + outfile = Path(tempfile.mkdtemp()) / filename + + with open(outfile, "wb") as f: + f.write(resolved_reference.read_bytes()) + + return outfile + + def add_viewpoint(self, element: entity_instance) -> None: + """Add a viewpoint pointed at the placement of an IFC element to the topic. Args: element: The IFC element. @@ -229,11 +277,11 @@ class TopicHandler: self.add_visinfo_handler(new_viewpoint) def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None: - """ - Add a viewpoint tergeting an IFC element to the topic. + """Add a viewpoint pointing at an XYZ point in space Args: - element: The IFC element. + position: the XYZ point in space + guids: one or more element GlobalIds. """ vi_handler = VisualizationInfoHandler.create_from_point_and_guids( position, *guids, xml_handler=self._xml_handler