diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index 671c81958a..d4de537955 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -38,7 +38,7 @@ import ifcopenshell.util.shape import bonsai.tool as tool from bonsai.bim.ifc import IfcStore, IFC_CONNECTED_TYPE from bonsai.tool.loader import OBJECT_DATA_TYPE -from typing import Dict, Union, Optional, Any, Literal +from typing import Dict, Union, Optional, Any, Literal, Iterable from ifcopenshell.util.shape import MatrixType @@ -319,16 +319,15 @@ class IfcImporter: tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) def process_element_filter(self) -> None: + elements: list[ifcopenshell.entity_instance] if self.ifc_import_settings.has_filter: - self.elements = self.ifc_import_settings.elements - if isinstance(self.elements, set): - self.elements = list(self.elements) + elements = list(self.ifc_import_settings.elements) # TODO: enable filtering for annotations else: if self.file.schema in ("IFC2X3", "IFC4"): - self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy") + elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy") else: - self.elements = self.file.by_type("IfcElement") + elements = self.file.by_type("IfcElement") drawing_groups = [g for g in self.file.by_type("IfcGroup") if g.ObjectType == "DRAWING"] drawing_annotations = set() @@ -338,16 +337,16 @@ class IfcImporter: self.annotations = set([a for a in self.file.by_type("IfcAnnotation")]) self.annotations -= drawing_annotations - self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")] + elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")] if self.ifc_import_settings.element_limit_mode == "UNLIMITED": - self.elements = set(self.elements) + self.elements = set(elements) else: offset = self.ifc_import_settings.element_offset offset_limit = offset + self.ifc_import_settings.element_limit - self.elements = set(self.elements[offset:offset_limit]) + self.elements = set(elements[offset:offset_limit]) if self.ifc_import_settings.has_filter or self.ifc_import_settings.element_limit_mode != "UNLIMITED": - self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements]) + self.element_types = {t for e in self.elements if (t := ifcopenshell.util.element.get_type(e))} else: self.element_types = set(self.file.by_type("IfcTypeProduct")) @@ -1168,13 +1167,13 @@ class IfcImportSettings: self.element_limit_mode = "UNLIMITED" self.element_offset = 0 self.element_limit = 30000 - self.has_filter = None + self.has_filter = False self.should_filter_spatial_elements = True self.should_setup_viewport_camera = True self.contexts: list[ifcopenshell.entity_instance] = [] self.context_settings: list[ifcopenshell.geom.main.settings] = [] self.gross_context_settings: list[ifcopenshell.geom.main.settings] = [] - self.elements: set[ifcopenshell.entity_instance] = set() + self.elements: Iterable[ifcopenshell.entity_instance] = set() self.load_indexed_maps = False @staticmethod diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index a7f6fa364f..17789759fe 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1736,13 +1736,14 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator): ) def join_blender_obj(self) -> None: + ifc_file = tool.Ifc.get() for obj in bpy.context.selected_objects: if obj == self.target: continue # TODO Properly handle element types, grid axes, and representation items element = tool.Ifc.get_entity(obj) if element: - ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element) + ifcopenshell.api.root.remove_product(ifc_file, product=element) bpy.ops.object.join() diff --git a/src/bonsai/bonsai/bim/module/geometry/prop.py b/src/bonsai/bonsai/bim/module/geometry/prop.py index 9b9bef081f..046be721e3 100644 --- a/src/bonsai/bonsai/bim/module/geometry/prop.py +++ b/src/bonsai/bonsai/bim/module/geometry/prop.py @@ -162,6 +162,11 @@ class RepresentationItemObject(PropertyGroup): obj: PointerProperty(type=bpy.types.Object) ifc_definition_id: IntProperty() + if TYPE_CHECKING: + name: str + obj: Union[bpy.types.Object, None] + ifc_definition_id: int + class ShapeAspect(PropertyGroup): name: StringProperty( diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 63e7ced454..604cb75da0 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -1103,8 +1103,8 @@ class LoadProjectElements(bpy.types.Operator): bonsai.bim.handler.refresh_ui_data() return {"FINISHED"} - def get_decomposition_elements(self): - containers = set() + def get_decomposition_elements(self) -> set[ifcopenshell.entity_instance]: + containers: set[ifcopenshell.entity_instance] = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue @@ -1116,15 +1116,15 @@ class LoadProjectElements(bpy.types.Operator): container = None elif self.file.schema != "IFC2X3" and container.is_a("IfcContext"): container = None - elements = set() + elements: set[ifcopenshell.entity_instance] = set() for container in containers: for rel in container.ContainsElements: elements.update(rel.RelatedElements) self.append_decomposed_elements(elements) return elements - def append_decomposed_elements(self, elements): - decomposed_elements = set() + def append_decomposed_elements(self, elements: set[ifcopenshell.entity_instance]) -> None: + decomposed_elements: set[ifcopenshell.entity_instance] = set() for element in elements: if element.IsDecomposedBy: for subelement in element.IsDecomposedBy[0].RelatedObjects: @@ -1133,26 +1133,26 @@ class LoadProjectElements(bpy.types.Operator): self.append_decomposed_elements(decomposed_elements) elements.update(decomposed_elements) - def get_ifc_class_elements(self): - elements = set() + def get_ifc_class_elements(self) -> set[ifcopenshell.entity_instance]: + elements: set[ifcopenshell.entity_instance] = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue elements.update(self.file.by_type(filter_category.name, include_subtypes=False)) return elements - def get_ifc_type_elements(self): - elements = set() + def get_ifc_type_elements(self) -> set[ifcopenshell.entity_instance]: + elements: set[ifcopenshell.entity_instance] = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue elements.update(ifcopenshell.util.element.get_types(self.file.by_id(filter_category.ifc_definition_id))) return elements - def get_whitelist_elements(self): + def get_whitelist_elements(self) -> set[ifcopenshell.entity_instance]: return set(ifcopenshell.util.selector.filter_elements(self.file, self.props.filter_query)) - def get_blacklist_elements(self): + def get_blacklist_elements(self) -> set[ifcopenshell.entity_instance]: return set(self.file.by_type("IfcElement")) - set( ifcopenshell.util.selector.filter_elements(self.file, self.props.filter_query) ) diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index d7510b0968..df4a146912 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -67,6 +67,7 @@ from typing import ( Generator, cast, TypeGuard, + Any, ) from typing_extensions import TypeIs @@ -1861,6 +1862,7 @@ class Geometry(bonsai.core.tool.Geometry): tool.Blender.set_active_object(props.representation_obj) cls.sync_item_positions() representation = cls.get_active_representation(props.representation_obj) + assert representation ifcopenshell.api.geometry.validate_type(tool.Ifc.get(), representation) props.is_changing_mode = True if props.mode != "OBJECT": @@ -1873,6 +1875,7 @@ class Geometry(bonsai.core.tool.Geometry): def edit_meshlike_item(cls, obj: bpy.types.Object) -> None: item = tool.Geometry.get_active_representation(obj) assert item + assert isinstance(obj.data, (bpy.types.Curve, bpy.types.Mesh)) mprops = tool.Geometry.get_mesh_props(obj.data) if mprops.mesh_checksum == cls.get_mesh_checksum(obj.data): return @@ -1898,7 +1901,7 @@ class Geometry(bonsai.core.tool.Geometry): ifcopenshell.util.element.replace_attribute(inverse, item, new_item) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item) tool.Ifc.link(new_item, obj.data) - cls.reload_representation(props.representation_obj) + cls.reload_representation(rep_obj) @classmethod def split_by_loose_parts(cls, obj: bpy.types.Object) -> List[bpy.types.Mesh]: @@ -2130,7 +2133,6 @@ class Geometry(bonsai.core.tool.Geometry): tool.Root.reload_grid_decorator() return old_to_new, new_active_obj or active_object - @classmethod def duplicate_ifc_item(cls, obj: bpy.types.Object) -> None: props = tool.Geometry.get_geometry_props() @@ -2148,11 +2150,13 @@ class Geometry(bonsai.core.tool.Geometry): for collection in obj.users_collection: collection.objects.link(new_obj) - representation = tool.Geometry.get_active_representation(props.representation_obj) + assert (rep_obj := props.representation_obj) + representation = tool.Geometry.get_active_representation(rep_obj) + assert representation representation = ifcopenshell.util.representation.resolve_representation(representation) representation.Items = list(representation.Items) + [new_item] - tool.Geometry.reload_representation(props.representation_obj) + tool.Geometry.reload_representation(rep_obj) obj.select_set(False) tool.Root.reload_item_decorator() diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py index 3848a0fb87..b5970e4d44 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py @@ -47,7 +47,7 @@ def validate_type( combination, or False otherwise. """ - def is_operand(item): + def is_operand(item: ifcopenshell.entity_instance) -> bool: return ( item.is_a("IfcBooleanResult") or item.is_a("IfcCsgPrimitive3D")