From ae2a2996167459c3ecdb10325c0706ddc5859677 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 1 Jul 2024 13:33:47 +0500 Subject: [PATCH] move IfcHeaderExtractor to util.file edited pyproject.toml as `disableBytesTypePromotions` is disabled by default in non-strict mode and type checker intrerprets `bytes` as `bytes | memoryview | bytearray` which is deprecated in https://peps.python.org/pep-0688/#no-special-meaning-for-bytes --- pyproject.toml | 1 + src/blenderbim/blenderbim/bim/helper.py | 48 -------- src/blenderbim/blenderbim/bim/ui.py | 2 +- .../ifcopenshell/util/file.py | 113 ++++++++++++++++++ 4 files changed, 115 insertions(+), 49 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/util/file.py diff --git a/pyproject.toml b/pyproject.toml index be5f678a9c..518164504f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,3 +3,4 @@ line-length = 120 [tool.pyright] reportInvalidTypeForm = false +disableBytesTypePromotions = true diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 0fb911234e..867e51b222 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -20,7 +20,6 @@ import bpy import json import math -import zipfile import ifcopenshell import ifcopenshell.util.attribute import ifcopenshell.util.element @@ -322,50 +321,3 @@ def draw_filter(layout, filter_groups, data, module): op.group_index = i op.index = j op.module = module - - -# TODO this should move into ifcopenshell.util - - -class IfcHeaderExtractor: - def __init__(self, filepath: str): - self.filepath = filepath - - def extract(self): - extension = self.filepath.split(".")[-1] - if extension.lower() == "ifc": - with open(self.filepath) as ifc_file: - return self.extract_ifc_spf(ifc_file) - elif extension.lower() == "ifczip": - return self.extract_ifc_zip() - elif extension.lower() == "ifcsqlite": - return {} # TODO - - def extract_ifc_spf(self, ifc_file): - # https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8 - data = {} - max_lines_to_parse = 50 - for _ in range(max_lines_to_parse): - line = next(ifc_file) - if isinstance(line, bytes): - line = line.decode("utf-8") - if line.startswith("FILE_DESCRIPTION"): - for i, part in enumerate(line.split("'")): - if i == 1: - data["description"] = part - elif i == 3: - data["implementation_level"] = part - elif line.startswith("FILE_NAME"): - for i, part in enumerate(line.split("'")): - if i == 1: - data["name"] = part - elif i == 3: - data["time_stamp"] = part - elif line.startswith("FILE_SCHEMA"): - data["schema_name"] = line.split("'")[1] - break - return data - - def extract_ifc_zip(self): - archive = zipfile.ZipFile(self.filepath, "r") - return self.extract_ifc_spf(archive.open(archive.filelist[0])) diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 4aaed962c7..880bbaf243 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -33,7 +33,7 @@ from . import ifc from blenderbim import get_debug_info import blenderbim.bim import blenderbim.tool as tool -from blenderbim.bim.helper import IfcHeaderExtractor +from ifcopenshell.util.file import IfcHeaderExtractor from blenderbim.bim.prop import Attribute from typing import Optional diff --git a/src/ifcopenshell-python/ifcopenshell/util/file.py b/src/ifcopenshell-python/ifcopenshell/util/file.py new file mode 100644 index 0000000000..709018e676 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/file.py @@ -0,0 +1,113 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import zipfile +from typing import IO, Union, TypedDict, NotRequired + + +class HeaderMetadata(TypedDict): + name: NotRequired[str] + description: NotRequired[str] + implementation_level: NotRequired[str] + time_stamp: NotRequired[str] + schema_name: NotRequired[str] + + +class IfcHeaderExtractor: + """An utility class for extracting header information from IFC files. + + This class provides functionality to extract key metadata from the header section of + IFC files without recreating the entire file as `ifcopenshell.file`. + For optimization, extractor will search only for the first 50 lines + of the IFC file for metadata. + + Supported formats: .ifc, .ifczip. + + Metadata available by the extraction is presented in the example below. + + + Example: + + .. code:: python + + from ifcopenshell.util.file import IfcHeaderExtractor + + extractor = IfcHeaderExtractor("path/to/your/file.ifc") + # Get dictionary of the extracted metadata. + header_info = extractor.extract() + + # Print the extracted information + + # ViewDefinition[DesignTransferView] + print("File Description:", header_info.get("description")) + + # 2;1 + print("Implementation Level:", header_info.get("implementation_level")) + + # file.ifc + print("File Name:", header_info.get("name")) + + # 2024-06-25T15:48:10+05:00 + print("Time Stamp:", header_info.get("time_stamp")) + + # IFC4X3_ADD2 + print("Schema Name:", header_info.get("schema_name")) + """ + + def __init__(self, filepath: str): + self.filepath = filepath + + def extract(self) -> HeaderMetadata: + extension = self.filepath.split(".")[-1] + if extension.lower() == "ifc": + with open(self.filepath) as ifc_file: + return self.extract_ifc_spf(ifc_file) + elif extension.lower() == "ifczip": + return self.extract_ifc_zip() + elif extension.lower() == "ifcsqlite": + return {} # TODO + raise ValueError(f"Unsupported file extension: '{extension}'.") + + def extract_ifc_spf(self, ifc_file: Union[IO[bytes], IO[str]]) -> HeaderMetadata: + # https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8 + data = HeaderMetadata() + max_lines_to_parse = 50 + for _ in range(max_lines_to_parse): + line = next(ifc_file) + if isinstance(line, bytes): + line = line.decode("utf-8") + if line.startswith("FILE_DESCRIPTION"): + for i, part in enumerate(line.split("'")): + if i == 1: + data["description"] = part + elif i == 3: + data["implementation_level"] = part + elif line.startswith("FILE_NAME"): + for i, part in enumerate(line.split("'")): + if i == 1: + data["name"] = part + elif i == 3: + data["time_stamp"] = part + elif line.startswith("FILE_SCHEMA"): + data["schema_name"] = line.split("'")[1] + break + return data + + def extract_ifc_zip(self) -> HeaderMetadata: + archive = zipfile.ZipFile(self.filepath, "r") + return self.extract_ifc_spf(archive.open(archive.filelist[0]))