diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 81f20c809e..f2e5532feb 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -26,6 +26,7 @@ import bmesh import logging import mathutils import numpy as np +import numpy.typing as npt import multiprocessing import ifcopenshell import ifcopenshell.geom @@ -303,22 +304,22 @@ class IfcImporter: self.update_progress(100) bpy.context.window_manager.progress_end() - def is_element_far_away(self, element): + def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool: try: placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) point = placement[:, 3][0:3] return self.is_point_far_away(point, is_meters=False) except: - pass + return False - def is_point_far_away(self, point, is_meters=True): + def is_point_far_away( + self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True + ) -> bool: # Locations greater than 1km are not considered "small sites" according to the georeferencing guide # Users can configure this if they have to handle larger sites but beware of surveying precision limit = self.ifc_import_settings.distance_limit limit = limit if is_meters else (limit / self.unit_scale) - coords = point - if hasattr(point, "Coordinates"): - coords = point.Coordinates + coords = getattr(point, "Coordinates", point) return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit def process_context_filter(self): @@ -683,7 +684,7 @@ class IfcImporter: props.blender_orthogonal_height = str(offset_point[2]) props.has_blender_offset = True - def get_offset_point(self): + def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]: elements_checked = 0 # If more than these elements aren't far away, the file probably isn't absolutely positioned element_checking_threshold = 10 @@ -718,7 +719,7 @@ class IfcImporter: if self.is_point_far_away(point, is_meters=False): return point - def does_element_likely_have_geometry_far_away(self, element): + def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool: for representation in element.Representation.Representations: items = [] for item in representation.Items: @@ -735,13 +736,14 @@ class IfcImporter: if subelement.is_a("IfcCartesianPoint"): if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False): return True + return False def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix: props = bpy.context.scene.BIMGeoreferenceProperties if props.has_blender_offset: if obj.data and obj.data.get("has_cartesian_point_offset", None): obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" - elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])): + elif self.is_point_far_away((matrix[:3, 3])): obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" matrix = ifcopenshell.util.geolocation.global2local( matrix, diff --git a/src/blenderbim/blenderbim/bim/module/misc/operator.py b/src/blenderbim/blenderbim/bim/module/misc/operator.py index 6806c3635a..ed2e714d32 100644 --- a/src/blenderbim/blenderbim/bim/module/misc/operator.py +++ b/src/blenderbim/blenderbim/bim/module/misc/operator.py @@ -23,6 +23,7 @@ import blenderbim.bim.handler import blenderbim.tool as tool import blenderbim.core.misc as core import blenderbim.core.geometry as core_geometry +import blenderbim.core.root from blenderbim.bim.ifc import IfcStore from mathutils import Vector, Matrix, Euler @@ -144,6 +145,7 @@ class SplitAlongEdge(bpy.types.Operator, Operator): cutter = context.active_object objs = [o for o in context.selected_objects if o != cutter] + objs_to_cut = [] # Splitting only works on meshes for obj in objs: # You cannot split meshes if the representation is mapped. @@ -153,7 +155,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator): if relating_type and tool.Root.does_type_have_representations(relating_type): bpy.ops.bim.unassign_type(related_object=obj.name) + # refresh representation representation = tool.Geometry.get_active_representation(obj) + + # skip empty objects that might get in the way + if not representation: + continue + core_geometry.switch_representation( tool.Ifc, tool.Geometry, @@ -168,11 +176,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator): if not tool.Geometry.is_meshlike(representation): bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet") - new_objs = tool.Misc.split_objects_with_cutter(objs, cutter) + objs_to_cut.append(obj) + + new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter) for obj in new_objs: blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) bpy.ops.bim.update_representation(obj=obj.name) - for obj in objs: + for obj in objs_to_cut: bpy.ops.bim.update_representation(obj=obj.name) representation = tool.Geometry.get_active_representation(obj) diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 7e68829c2a..e2b27cc35e 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -18,10 +18,11 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional -import blenderbim.core.tool as tool if TYPE_CHECKING: import bpy + import ifcopenshell + import blenderbim.tool as tool def edit_object_placement( @@ -37,8 +38,15 @@ def edit_object_placement( def add_representation( - ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None -): + ifc: tool.Ifc, + geometry: tool.Geometry, + style: tool.Style, + surveyor: tool.Surveyor, + obj: bpy.types.Object, + context: ifcopenshell.entity_instance, + ifc_representation_class: Optional[str] = None, + profile_set_usage: Optional[ifcopenshell.entity_instance] = None, +) -> ifcopenshell.entity_instance: element = ifc.get_entity(obj) if not element: return @@ -89,15 +97,15 @@ def add_representation( def switch_representation( - ifc, - geometry, - obj=None, - representation=None, - should_reload=True, - is_global=True, - should_sync_changes_first=False, - apply_openings=True, -): + ifc: tool.Ifc, + geometry: tool.Geometry, + obj: bpy.types.Object, + representation: ifcopenshell.entity_instance, + should_reload: bool = True, + is_global: bool = True, + should_sync_changes_first: bool = False, + apply_openings: bool = True, +) -> None: """Function can switch to representation that wasn't yet assigned to that object. See #2766. `should_sync_changes_first` - sync ifc representation with current state of `obj.data`; diff --git a/src/blenderbim/blenderbim/core/root.py b/src/blenderbim/blenderbim/core/root.py index 3f36c39adb..e7f6bbb96c 100644 --- a/src/blenderbim/blenderbim/core/root.py +++ b/src/blenderbim/blenderbim/core/root.py @@ -16,8 +16,18 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +from __future__ import annotations +from typing import TYPE_CHECKING, Optional -def copy_class(ifc, collector, geometry, root, obj=None): +if TYPE_CHECKING: + import bpy + import ifcopenshell + import blenderbim.tool as tool + + +def copy_class( + ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object +) -> ifcopenshell.entity_instance: element = ifc.get_entity(obj) if not element: return @@ -48,16 +58,16 @@ def copy_class(ifc, collector, geometry, root, obj=None): def assign_class( - ifc, - collector, - root, - obj=None, - ifc_class=None, - predefined_type=None, - should_add_representation=True, - context=None, - ifc_representation_class=None, -): + ifc: tool.Ifc, + collector: tool.Collector, + root: tool.Root, + obj: bpy.types.Object, + ifc_class: str, + context: ifcopenshell.entity_instance, + predefined_type: Optional[str] = None, + should_add_representation: bool = True, + ifc_representation_class: Optional[str] = None, +) -> ifcopenshell.entity_instance: if ifc.get_entity(obj): return diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index 139eeef46a..8a45ff8225 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -18,11 +18,11 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -import blenderbim.core.tool as tool if TYPE_CHECKING: import bpy import ifcopenshell + import blenderbim.tool as tool def reference_structure( diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index ba2896839b..77c8c7eefb 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -26,6 +26,7 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.guid import ifcopenshell.util.element +import ifcopenshell.util.representation import ifcopenshell.util.system import blenderbim.core.tool import blenderbim.core.drawing @@ -319,7 +320,7 @@ class Geometry(blenderbim.core.tool.Geometry): return new_mesh @classmethod - def get_active_representation(cls, obj): + def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: """< IfcShapeRepresentation or None""" if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) @@ -459,7 +460,9 @@ class Geometry(blenderbim.core.tool.Geometry): return f"{representation.ContextOfItems.id()}/{representation.id()}" @classmethod - def get_styles(cls, obj, only_assigned_to_faces=False): + def get_styles( + cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False + ) -> list[Union[ifcopenshell.entity_instance, None]]: styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material] if not only_assigned_to_faces: return styles @@ -572,11 +575,11 @@ class Geometry(blenderbim.core.tool.Geometry): return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs @classmethod - def is_mapped_representation(cls, representation): + def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool: return representation.RepresentationType == "MappedRepresentation" @classmethod - def is_meshlike(cls, representation): + def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool: if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in ( "AdvancedBrep", "Annotation2D", @@ -656,7 +659,9 @@ class Geometry(blenderbim.core.tool.Geometry): bpy.data.objects.remove(obj) @classmethod - def resolve_mapped_representation(cls, representation): + def resolve_mapped_representation( + cls, representation: ifcopenshell.entity_instance + ) -> ifcopenshell.entity_instance: if representation.RepresentationType == "MappedRepresentation": return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation) return representation diff --git a/src/blenderbim/blenderbim/tool/misc.py b/src/blenderbim/blenderbim/tool/misc.py index 293e877a09..68e263eaaf 100644 --- a/src/blenderbim/blenderbim/tool/misc.py +++ b/src/blenderbim/blenderbim/tool/misc.py @@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc): IfcStore.edited_objs.add(obj) @classmethod - def split_objects_with_cutter(cls, objs, cutter): + def split_objects_with_cutter( + cls, objs: list[bpy.types.Object], cutter: bpy.types.Object + ) -> list[bpy.types.Object]: cutter_mesh = cutter.data bm = bmesh.new() diff --git a/src/blenderbim/blenderbim/tool/project.py b/src/blenderbim/blenderbim/tool/project.py index a73ae431a8..9c8cd7aaaa 100644 --- a/src/blenderbim/blenderbim/tool/project.py +++ b/src/blenderbim/blenderbim/tool/project.py @@ -21,6 +21,7 @@ import bpy import ifcopenshell import ifcopenshell.util.unit import blenderbim.core.tool +import blenderbim.core.root import blenderbim.bim.schema import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 8a9273eb10..e83c470bbd 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -29,6 +29,7 @@ import blenderbim.core.style import blenderbim.tool as tool from mathutils import Vector from blenderbim.bim.module.model.opening import FilledOpeningGenerator +from typing import Union, Optional class Root(blenderbim.core.tool.Root): @@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root): return ifcopenshell.util.representation.get_representation(element, context=context.ContextType) @classmethod - def get_element_type(cls, element): + def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: return ifcopenshell.util.element.get_type(element) @classmethod @@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root): @classmethod def run_geometry_add_representation( - cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None - ): + cls, + obj: bpy.types.Object, + context: ifcopenshell.entity_instance, + ifc_representation_class: Optional[str] = None, + profile_set_usage: Optional[ifcopenshell.entity_instance] = None, + ) -> ifcopenshell.entity_instance: return blenderbim.core.geometry.add_representation( tool.Ifc, tool.Geometry, diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 60ac89044c..c192e037e9 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -446,13 +446,13 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> str: return predefined_type -def get_type(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: +def get_type(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: """Retrieves the construction type element of an element occurrence :param element: The element occurrence :type: ifcopenshell.entity_instance :return: The related type element - :rtype: ifcopenshell.entity_instance + :rtype: Union[ifcopenshell.entity_instance, None] Example: