diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 2a68da048a..00e7337d96 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -38,8 +38,9 @@ import blenderbim.tool as tool import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper from itertools import chain, accumulate from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE +from blenderbim.tool.loader import OBJECT_DATA_TYPE from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA -from typing import Dict, Union, Optional +from typing import Dict, Union, Optional, Any class MaterialCreator: @@ -855,10 +856,10 @@ class IfcImporter: self.create_product(element, mesh=mesh) print("Done creating geometry") - def create_spatial_elements(self): - self.create_generic_elements(self.spatial_elements) + def create_spatial_elements(self) -> None: + self.create_generic_elements(self.spatial_elements, unselectable=True) - def create_elements(self): + def create_elements(self) -> None: self.create_generic_elements(self.elements) tmp = self.context_settings self.context_settings = self.gross_context_settings @@ -876,7 +877,7 @@ class IfcImporter: except: pass - def create_generic_elements(self, elements: set[ifcopenshell.entity_instance]) -> None: + def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], unselectable=False) -> None: if isinstance(self.file, ifcopenshell.sqlite): return self.create_generic_sqlite_elements(elements) @@ -1108,7 +1109,12 @@ class IfcImporter: self.link_element(product, obj) return product - def create_product(self, element, shape=None, mesh=None): + def create_product( + self, + element: ifcopenshell.entity_instance, + shape: Optional[Any] = None, + mesh: Optional[OBJECT_DATA_TYPE] = None, + ) -> Union[bpy.types.Object, None]: if element is None: return @@ -1833,7 +1839,7 @@ class IfcImporter: ): return representation.Items[0].MappingTarget - def create_curve(self, element, shape): + def create_curve(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Curve: if hasattr(shape, "geometry"): geometry = shape.geometry else: diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 3ffb807f98..aac640d4da 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -26,6 +26,7 @@ import os import numpy as np from mathutils import Vector from pathlib import Path +from typing import Union # Progressively we'll refactor loading elements into Blender objects into this @@ -34,6 +35,9 @@ from pathlib import Path # supplementary objects (e.g. drawings, structural analysis models, etc). +OBJECT_DATA_TYPE = Union[bpy.types.Mesh, bpy.types.Curve] + + class Loader(blenderbim.core.tool.Loader): @classmethod def create_project_collection(cls, name: str) -> bpy.types.Collection: @@ -51,7 +55,7 @@ class Loader(blenderbim.core.tool.Loader): return collection @classmethod - def get_mesh_name(cls, geometry): + def get_mesh_name(cls, geometry) -> str: representation_id = geometry.id if "-" in representation_id: representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0])) @@ -62,11 +66,11 @@ class Loader(blenderbim.core.tool.Loader): return "{}/{}".format(context_id, representation_id) @classmethod - def get_name(cls, element): + def get_name(cls, element: ifcopenshell.entity_instance) -> str: return "{}/{}".format(element.is_a(), getattr(element, "Name", "None")) @classmethod - def link_mesh(cls, shape, mesh): + def link_mesh(cls, shape, mesh: OBJECT_DATA_TYPE) -> None: geometry = shape.geometry if hasattr(shape, "geometry") else shape if "-" in geometry.id: mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0]) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 35f97ae3cd..03900607b1 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -18,7 +18,7 @@ from fractions import Fraction from math import pi -from typing import Tuple, Iterable, Any +from typing import Tuple, Iterable, Any, Union, Literal import ifcopenshell import ifcopenshell.api @@ -599,14 +599,14 @@ def calculate_unit_scale(ifc_file, unit_type="LENGTHUNIT"): def format_length( - value, - precision, - decimal_places=2, + value: float, + precision: float, + decimal_places: int = 2, suppress_zero_inches=True, - unit_system="imperial", + unit_system: Union[Literal["metric"], Literal["imperial"]] = "imperial", input_unit="foot", output_unit="foot", -): +) -> str: """Formats a length for readability and imperial formatting :param value: The value in meters if metric, or either decimal feet or @@ -670,7 +670,7 @@ def format_length( def is_attr_type( content_type: ifcopenshell.ifcopenshell_wrapper.named_type | ifcopenshell.ifcopenshell_wrapper.type_declaration, ifc_unit_type_name: str, -) -> ifcopenshell.ifcopenshell_wrapper.type_declaration | None: +) -> Union[ifcopenshell.ifcopenshell_wrapper.type_declaration, None]: cur_decl = content_type while hasattr(cur_decl, "declared_type") is True: cur_decl = cur_decl.declared_type() diff --git a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py index 48da1e1b37..7483569122 100644 --- a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py +++ b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py @@ -22,16 +22,17 @@ import ifcopenshell.api.owner.settings import ifcopenshell.util.pset import ifcopenshell.util.element import ifcopenshell.util.unit +from logging import Logger class Patcher: - def __init__(self, src, file, logger, unit="METERS"): + def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, unit: str = "METERS"): """Converts the length unit of a model to the specified unit Allowed metric units include METERS, MILLIMETERS, CENTIMETERS, etc. Allowed imperial units include INCHES, FEET, MILES. - :param unit: The name of the desired unit. + :param unit: The name of the desired unit, defaults to "METERS" :type unit: str Example: @@ -48,7 +49,7 @@ class Patcher: self.file = file self.logger = logger self.unit = unit - self.file_patched: ifcopenshell.file = None + self.file_patched: ifcopenshell.file def patch(self): self.file_patched = ifcopenshell.util.unit.convert_file_length_units(self.file, self.unit) diff --git a/src/ifcpatch/ifcpatch/recipes/MergeProject.py b/src/ifcpatch/ifcpatch/recipes/MergeProject.py index 586e8a4eb7..260affcfa3 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeProject.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeProject.py @@ -18,10 +18,12 @@ import ifcopenshell import ifcopenshell.util.element +from typing import Union +from logging import Logger class Patcher: - def __init__(self, src, file, logger, filepath=None): + def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, filepath: str): """Merge two IFC models into one Note that other than combining the two IfcProject elements into one, no @@ -46,18 +48,25 @@ class Patcher: def patch(self): source = ifcopenshell.open(self.filepath) - self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext") - self.added_contexts = set() + self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type( + "IfcGeometricRepresentationContext" + ) + self.added_contexts: set[ifcopenshell.entity_instance] = set() + original_project = self.file.by_type("IfcProject")[0] merged_project = self.file.add(source.by_type("IfcProject")[0]) + for element in source.by_type("IfcGeometricRepresentationContext"): new = self.file.add(element) self.added_contexts.add(new) + for element in source: self.file.add(element) + for inverse in self.file.get_inverse(merged_project): ifcopenshell.util.element.replace_attribute(inverse, merged_project, original_project) self.file.remove(merged_project) + self.reuse_existing_contexts() def reuse_existing_contexts(self): @@ -73,7 +82,9 @@ class Patcher: for added_context in to_delete: ifcopenshell.util.element.remove_deep2(self.file, added_context) - def get_equivalent_existing_context(self, added_context): + def get_equivalent_existing_context( + self, added_context: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: for context in self.existing_contexts: if context.is_a() != added_context.is_a(): continue diff --git a/src/ifcpatch/ifcpatch/recipes/Migrate.py b/src/ifcpatch/ifcpatch/recipes/Migrate.py index a82230a92f..5280875ce9 100644 --- a/src/ifcpatch/ifcpatch/recipes/Migrate.py +++ b/src/ifcpatch/ifcpatch/recipes/Migrate.py @@ -18,10 +18,11 @@ import ifcopenshell import ifcopenshell.util.schema +from logging import Logger class Patcher: - def __init__(self, src, file, logger, schema="IFC4"): + def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, schema: str = "IFC4"): """Migrate from one IFC version to another Note that this is experimental and will try to preserve as much data as diff --git a/src/ifcpatch/ifcpatch/recipes/PurgeData.py b/src/ifcpatch/ifcpatch/recipes/PurgeData.py index e2cdc4d140..946b8f19b4 100644 --- a/src/ifcpatch/ifcpatch/recipes/PurgeData.py +++ b/src/ifcpatch/ifcpatch/recipes/PurgeData.py @@ -17,10 +17,12 @@ # along with IfcPatch. If not, see . import datetime +import ifcopenshell +from logging import Logger class Patcher: - def __init__(self, src, file, logger): + def __init__(self, src: str, file: ifcopenshell.file, logger: Logger): """Purge IFC properties, relationships, and other data In some rare cases (i.e. "resetting" a model or for security purposes)