diff --git a/src/blenderbim/blenderbim/bim/module/model/covering.py b/src/blenderbim/blenderbim/bim/module/model/covering.py index bca60ed16c..c9de0b8ccc 100644 --- a/src/blenderbim/blenderbim/bim/module/model/covering.py +++ b/src/blenderbim/blenderbim/bim/module/model/covering.py @@ -32,7 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato @classmethod def poll(cls, context): - relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) + relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) return relating_type == "FLOORING" @@ -51,7 +51,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator @classmethod def poll(cls, context): - relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) + relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) return relating_type == "CEILING" @@ -91,7 +91,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato @classmethod def poll(cls, context): element = tool.Ifc.get_entity(bpy.context.active_object) - relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) + relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2": return context.selected_objects and relating_type == "FLOORING" @@ -119,7 +119,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator @classmethod def poll(cls, context): element = tool.Ifc.get_entity(bpy.context.active_object) - relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) + relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2": return context.selected_objects and relating_type == "CEILING" diff --git a/src/blenderbim/blenderbim/core/covering.py b/src/blenderbim/blenderbim/core/covering.py index fd85fa4aed..123da50899 100644 --- a/src/blenderbim/blenderbim/core/covering.py +++ b/src/blenderbim/blenderbim/core/covering.py @@ -16,8 +16,16 @@ # 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 add_instance_flooring_covering_from_cursor(ifc, root, spatial): +if TYPE_CHECKING: + import bpy + import ifcopenshell + import blenderbim.tool as tool + + +def add_instance_flooring_covering_from_cursor(ifc: tool.Ifc, root: tool.Root, spatial: tool.Spatial) -> None: if not root.get_default_container(): raise NoDefaultContainer() @@ -32,7 +40,7 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial): relating_type = None if selected_objects and active_obj: - x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj) else: x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() @@ -59,7 +67,9 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial): spatial.regen_obj_representation(obj, body) -def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial): +def add_instance_ceiling_covering_from_cursor( + ifc: tool.Ifc, root: tool.Root, covering: tool.Covering, spatial: tool.Spatial +) -> None: if not root.get_default_container(): raise NoDefaultContainer() @@ -74,7 +84,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial): relating_type = None if selected_objects and active_obj: - x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj) else: x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ceiling_height = covering.get_z_from_ceiling_height() @@ -101,7 +111,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial): spatial.regen_obj_representation(obj, body) -def regen_selected_covering_object(root, spatial): +def regen_selected_covering_object(root: tool.Root, spatial: tool.Spatial) -> None: if not root.get_default_container(): raise NoDefaultContainer() @@ -109,7 +119,7 @@ def regen_selected_covering_object(root, spatial): selected_objects = spatial.get_selected_objects() if selected_objects and active_obj: - x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj) space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y) @@ -132,7 +142,7 @@ def regen_selected_covering_object(root, spatial): # TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS -def add_instance_flooring_coverings_from_walls(root, spatial): +def add_instance_flooring_coverings_from_walls(root: tool.Root, spatial: tool.Spatial) -> None: if not root.get_default_container(): raise NoDefaultContainer() @@ -158,7 +168,7 @@ def add_instance_flooring_coverings_from_walls(root, spatial): spatial.regen_obj_representation(obj, body) -def add_instance_ceiling_coverings_from_walls(root, spatial, covering): +def add_instance_ceiling_coverings_from_walls(root: tool.Root, spatial: tool.Spatial, covering: tool.Covering) -> None: if not root.get_default_container(): raise NoDefaultContainer() diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index f7e3c23d55..a8c19fa42a 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -18,11 +18,12 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Union if TYPE_CHECKING: import bpy import ifcopenshell + import ifcopenshell.util.representation import blenderbim.tool as tool @@ -225,7 +226,11 @@ def disable_editing_drawings(drawing: tool.Drawing) -> None: def add_drawing( - ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None + ifc: tool.Ifc, + collector: tool.Collector, + drawing: tool.Drawing, + target_view: Union[ifcopenshell.util.representation.TARGET_VIEW, None] = None, + location_hint: Union[str, None] = None, ) -> None: drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint)) drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint) diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index e59aa042d0..7ff8457309 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -173,7 +173,7 @@ def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: t relating_type = None if selected_objects and active_obj: - x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) + x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj) element = ifc.get_entity(active_obj) else: x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 2455f74ae1..261029e27f 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -888,7 +888,7 @@ class Spatial: def get_boundary_lines_from_context_visible_objects(cls): pass def get_gross_mesh_from_element(cls, visible_element): pass def create_mesh_from_shape(cls, shape): pass - def get_x_y_z_h_mat_from_active_obj(cls, active_obj): pass + def get_x_y_z_h_mat_from_obj(cls, obj): pass def get_x_y_z_h_mat_from_cursor(cls): pass def get_union_shape_from_selected_objects(cls): pass def get_boundary_elements(cls, selected_objects): pass diff --git a/src/blenderbim/blenderbim/tool/covering.py b/src/blenderbim/blenderbim/tool/covering.py index 5268a5b1e7..7a76d0d4be 100644 --- a/src/blenderbim/blenderbim/tool/covering.py +++ b/src/blenderbim/blenderbim/tool/covering.py @@ -33,7 +33,7 @@ from shapely import Polygon, MultiPolygon class Covering(blenderbim.core.tool.Covering): @classmethod - def get_z_from_ceiling_height(cls): + def get_z_from_ceiling_height(cls) -> float: props = bpy.context.scene.BIMCoveringProperties return props.ceiling_height diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index ea78066650..b8d18d689f 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -19,6 +19,7 @@ import os import re import collections +import collections.abc import bpy import math import json @@ -79,11 +80,11 @@ class Drawing(blenderbim.core.tool.Drawing): # fmt: on @classmethod - def canonicalise_class_name(cls, name): + def canonicalise_class_name(cls, name: str) -> str: return re.sub("[^0-9a-zA-Z]+", "", name) @classmethod - def copy_representation(cls, source, dest): + def copy_representation(cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance) -> None: if source.Representation: dest.Representation = ifcopenshell.util.element.copy_deep( tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] @@ -114,7 +115,9 @@ class Drawing(blenderbim.core.tool.Drawing): return obj @classmethod - def ensure_annotation_in_drawing_plane(cls, obj, camera=None): + def ensure_annotation_in_drawing_plane( + cls, obj: bpy.types.Object, camera: Optional[bpy.types.Object] = None + ) -> None: """Make sure annotation object is going to be visible in the camera view""" def get_camera_from_annotation_object(obj): @@ -138,7 +141,9 @@ class Drawing(blenderbim.core.tool.Drawing): ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA") @classmethod - def setup_annotation_object(cls, obj, object_type, related_object=None): + def setup_annotation_object( + cls, obj: bpy.types.Object, object_type: str, related_object: Optional[bpy.types.Object] = None + ) -> None: """Finish object's adjustments after both object and entity are created""" if not related_object: @@ -205,7 +210,9 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Drawing.update_text_value(obj) @classmethod - def is_annotation_object_type(cls, element, object_types): + def is_annotation_object_type( + cls, element: ifcopenshell.entity_instance, object_types: Union[str, list[str]] + ) -> bool: if not isinstance(object_types, collections.abc.Iterable): object_types = [object_types] @@ -226,7 +233,9 @@ class Drawing(blenderbim.core.tool.Drawing): return False @classmethod - def get_annotation_representation(cls, element): + def get_annotation_representation( + cls, element: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: rep = ifcopenshell.util.representation.get_representation( element, "Plan", "Annotation" ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation") @@ -237,7 +246,9 @@ class Drawing(blenderbim.core.tool.Drawing): return rep @classmethod - def create_camera(cls, name, matrix, location_hint): + def create_camera( + cls, name: str, matrix: Matrix, location_hint: Literal["PERPSECTIVE", "ORTHOGRAPHIC"] + ) -> bpy.types.Object: camera = bpy.data.objects.new(name, bpy.data.cameras.new(name)) camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera.data.show_limits = True @@ -257,7 +268,7 @@ class Drawing(blenderbim.core.tool.Drawing): return camera @classmethod - def create_svg_schedule(cls, schedule): + def create_svg_schedule(cls, schedule: ifcopenshell.entity_instance) -> None: import blenderbim.bim.module.drawing.scheduler as scheduler schedule_creator = scheduler.Scheduler() @@ -276,7 +287,7 @@ class Drawing(blenderbim.core.tool.Drawing): return uri @classmethod - def add_drawings(cls, sheet): + def add_drawings(cls, sheet: ifcopenshell.entity_instance) -> None: import blenderbim.bim.module.drawing.sheeter as sheeter sheet_builder = sheeter.SheetBuilder() @@ -293,7 +304,7 @@ class Drawing(blenderbim.core.tool.Drawing): sheet_builder.add_drawing(drawing_references[drawing_annotation.Name], drawing_annotation, sheet) @classmethod - def delete_collection(cls, collection): + def delete_collection(cls, collection: bpy.types.Collection) -> None: bpy.data.collections.remove(collection, do_unlink=True) @classmethod @@ -312,19 +323,19 @@ class Drawing(blenderbim.core.tool.Drawing): bpy.data.objects.remove(obj) @classmethod - def disable_editing_drawings(cls): + def disable_editing_drawings(cls) -> None: bpy.context.scene.DocProperties.is_editing_drawings = False @classmethod - def disable_editing_schedules(cls): + def disable_editing_schedules(cls) -> None: bpy.context.scene.DocProperties.is_editing_schedules = False @classmethod - def disable_editing_references(cls): + def disable_editing_references(cls) -> None: bpy.context.scene.DocProperties.is_editing_references = False @classmethod - def disable_editing_sheets(cls): + def disable_editing_sheets(cls) -> None: bpy.context.scene.DocProperties.is_editing_sheets = False @classmethod @@ -344,19 +355,19 @@ class Drawing(blenderbim.core.tool.Drawing): bpy.ops.object.mode_set(mode="EDIT") @classmethod - def enable_editing_drawings(cls): + def enable_editing_drawings(cls) -> None: bpy.context.scene.DocProperties.is_editing_drawings = True @classmethod - def enable_editing_schedules(cls): + def enable_editing_schedules(cls) -> None: bpy.context.scene.DocProperties.is_editing_schedules = True @classmethod - def enable_editing_references(cls): + def enable_editing_references(cls) -> None: bpy.context.scene.DocProperties.is_editing_references = True @classmethod - def enable_editing_sheets(cls): + def enable_editing_sheets(cls) -> None: bpy.context.scene.DocProperties.is_editing_sheets = True @classmethod @@ -368,14 +379,14 @@ class Drawing(blenderbim.core.tool.Drawing): obj.BIMAssignedProductProperties.is_editing_product = True @classmethod - def ensure_unique_drawing_name(cls, name): + def ensure_unique_drawing_name(cls, name: str) -> str: names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] while name in names: name += "-X" return name @classmethod - def ensure_unique_identification(cls, identification): + def ensure_unique_identification(cls, identification: str) -> str: if tool.Ifc.get_schema() == "IFC2X3": ids = [d.DocumentId for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"] else: @@ -431,7 +442,7 @@ class Drawing(blenderbim.core.tool.Drawing): return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", target_view) @classmethod - def get_body_context(cls): + def get_body_context(cls) -> ifcopenshell.entity_instance: return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") @classmethod @@ -462,15 +473,15 @@ class Drawing(blenderbim.core.tool.Drawing): return os.path.splitext(os.path.basename(path))[0] @classmethod - def get_path_with_ext(cls, path, ext): + def get_path_with_ext(cls, path: str, ext: str) -> str: return os.path.splitext(path)[0] + f".{ext}" @classmethod - def get_unit_system(cls): + def get_unit_system(cls) -> Literal["NONE", "METRIC", "IMPERIAL"]: return bpy.context.scene.unit_settings.system @classmethod - def get_drawing_collection(cls, drawing): + def get_drawing_collection(cls, drawing: ifcopenshell.entity_instance) -> Union[bpy.types.Collection, None]: obj = tool.Ifc.get_object(drawing) if obj: return obj.BIMObjectProperties.collection @@ -488,8 +499,8 @@ class Drawing(blenderbim.core.tool.Drawing): return rel.RelatingDocument @classmethod - def get_drawing_references(cls, drawing): - results = set() + def get_drawing_references(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]: + results: set[ifcopenshell.entity_instance] = set() for inverse in tool.Ifc.get().get_inverse(drawing): if inverse.is_a("IfcRelAssignsToProduct") and inverse.RelatingProduct == drawing: results.update(inverse.RelatedObjects) @@ -505,7 +516,7 @@ class Drawing(blenderbim.core.tool.Drawing): return rel.RelatedObjects @classmethod - def get_ifc_representation_class(cls, object_type): + def get_ifc_representation_class(cls, object_type: str) -> str: if object_type == "TEXT": return "IfcTextLiteral" elif object_type == "TEXT_LEADER": @@ -517,7 +528,9 @@ class Drawing(blenderbim.core.tool.Drawing): return element.Name @classmethod - def generate_drawing_matrix(cls, target_view, location_hint): + def generate_drawing_matrix( + cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str + ) -> Matrix: x, y, z = (0, 0, 0) if location_hint == 0 else bpy.context.scene.cursor.matrix.translation if target_view == "PLAN_VIEW": if location_hint: @@ -554,12 +567,14 @@ class Drawing(blenderbim.core.tool.Drawing): return mathutils.Matrix() @classmethod - def generate_sheet_identification(cls): + def generate_sheet_identification(cls) -> str: number = len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]) return "A" + str(number).zfill(2) @classmethod - def get_text_literal(cls, obj, return_list=False): + def get_text_literal( + cls, obj: bpy.types.Object, return_list: bool = False + ) -> Union[ifcopenshell.entity_instance, None, list[ifcopenshell.entity_instance]]: element = tool.Ifc.get_entity(obj) if not element: return @@ -575,11 +590,11 @@ class Drawing(blenderbim.core.tool.Drawing): return items[0] @classmethod - def is_editing_sheets(cls): + def is_editing_sheets(cls) -> bool: return bpy.context.scene.DocProperties.is_editing_sheets @classmethod - def remove_literal_from_annotation(cls, obj, literal): + def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None: element = tool.Ifc.get_entity(obj) if not element: return @@ -617,7 +632,9 @@ class Drawing(blenderbim.core.tool.Drawing): cls.remove_literal_from_annotation(obj, literal) @classmethod - def add_literal_to_annotation(cls, obj, Literal="Literal", Path="RIGHT", BoxAlignment="bottom-left"): + def add_literal_to_annotation( + cls, obj: bpy.types.Object, Literal: str = "Literal", Path: str = "RIGHT", BoxAlignment: str = "bottom-left" + ) -> Union[ifcopenshell.entity_instance, None]: element = tool.Ifc.get_entity(obj) if not element: return @@ -831,7 +848,7 @@ class Drawing(blenderbim.core.tool.Drawing): new.identification = schedule.Identification @classmethod - def import_sheets(cls): + def import_sheets(cls) -> None: props = bpy.context.scene.DocProperties expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded} props.sheets.clear() @@ -868,7 +885,7 @@ class Drawing(blenderbim.core.tool.Drawing): new.reference_type = reference_description @classmethod - def get_active_sheet(cls, context): + def get_active_sheet(cls, context: bpy.types.Context) -> bpy.types.PropertyGroup: props = context.scene.DocProperties return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet) @@ -904,7 +921,7 @@ class Drawing(blenderbim.core.tool.Drawing): obj.BIMAssignedProductProperties.relating_product = None @classmethod - def open_with_user_command(cls, user_command, path): + def open_with_user_command(cls, user_command: str, path: str) -> None: if user_command: commands = json.loads(user_command) replacements = {"path": path} @@ -920,15 +937,15 @@ class Drawing(blenderbim.core.tool.Drawing): subprocess.call(("xdg-open", path)) @classmethod - def open_spreadsheet(cls, uri): + def open_spreadsheet(cls, uri: str) -> None: cls.open_with_user_command(tool.Blender.get_addon_preferences().spreadsheet_command, uri) @classmethod - def open_svg(cls, uri): + def open_svg(cls, uri: str) -> None: cls.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, uri) @classmethod - def open_layout_svg(cls, uri): + def open_layout_svg(cls, uri: str) -> None: cls.open_with_user_command(tool.Blender.get_addon_preferences().layout_svg_command, uri) @classmethod @@ -954,15 +971,17 @@ class Drawing(blenderbim.core.tool.Drawing): ) @classmethod - def set_drawing_collection_name(cls, drawing, collection): + def set_drawing_collection_name( + cls, drawing: ifcopenshell.entity_instance, collection: bpy.types.Collection + ) -> None: collection.name = tool.Loader.get_name(drawing) @classmethod - def set_name(cls, element, name): + def set_name(cls, element: ifcopenshell.entity_instance, name: str) -> None: element.Name = name @classmethod - def show_decorations(cls): + def show_decorations(cls) -> None: bpy.context.scene.DocProperties.should_draw_decorations = True @classmethod @@ -1013,15 +1032,15 @@ class Drawing(blenderbim.core.tool.Drawing): # TODO below this point is highly experimental prototype code with no tests @classmethod - def does_file_exist(cls, uri): + def does_file_exist(cls, uri: str) -> bool: return os.path.exists(uri) @classmethod - def delete_file(cls, uri): + def delete_file(cls, uri: str) -> None: os.remove(uri) @classmethod - def move_file(cls, src, dest): + def move_file(cls, src: str, dest: str) -> None: try: shutil.move(src, dest) except: @@ -1029,7 +1048,9 @@ class Drawing(blenderbim.core.tool.Drawing): shutil.copy(src, dest) @classmethod - def generate_drawing_name(cls, target_view, location_hint): + def generate_drawing_name( + cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str + ) -> str: if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint: location = tool.Ifc.get().by_id(location_hint) if target_view == "REFLECTED_PLAN_VIEW": @@ -1042,7 +1063,7 @@ class Drawing(blenderbim.core.tool.Drawing): return target_view @classmethod - def get_default_layout_path(cls, identification, name): + def get_default_layout_path(cls, identification: str, name: str) -> str: project = tool.Ifc.get().by_type("IfcProject")[0] layouts_dir = ( ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") @@ -1051,7 +1072,7 @@ class Drawing(blenderbim.core.tool.Drawing): return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") @classmethod - def get_default_sheet_path(cls, identification, name): + def get_default_sheet_path(cls, identification: str, name: str) -> str: project = tool.Ifc.get().by_type("IfcProject")[0] sheets_dir = ( ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") @@ -1060,7 +1081,7 @@ class Drawing(blenderbim.core.tool.Drawing): return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") @classmethod - def get_default_titleblock_path(cls, name): + def get_default_titleblock_path(cls, name: str) -> str: project = tool.Ifc.get().by_type("IfcProject")[0] titleblocks_dir = ( ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") @@ -1069,7 +1090,7 @@ class Drawing(blenderbim.core.tool.Drawing): return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") @classmethod - def get_default_drawing_path(cls, name): + def get_default_drawing_path(cls, name: str) -> str: project = tool.Ifc.get().by_type("IfcProject")[0] drawings_dir = ( ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") @@ -1078,11 +1099,11 @@ class Drawing(blenderbim.core.tool.Drawing): return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") @classmethod - def sanitise_filename(cls, name): + def sanitise_filename(cls, name: str) -> str: return "".join(x for x in name if (x.isalnum() or x in "._- ")) @classmethod - def get_default_drawing_resource_path(cls, resource): + def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]: project = tool.Ifc.get().by_type("IfcProject")[0] resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr( bpy.context.scene.DocProperties, f"{resource.lower()}_path" @@ -1091,12 +1112,12 @@ class Drawing(blenderbim.core.tool.Drawing): return resource_path.replace("\\", "/") @classmethod - def get_default_shading_style(cls): + def get_default_shading_style(cls) -> str: dprops = bpy.context.scene.DocProperties return dprops.shadingstyle_default @classmethod - def setup_shading_styles_path(cls, resource_path): + def setup_shading_styles_path(cls, resource_path: str) -> None: resource_path = tool.Ifc.resolve_uri(resource_path) os.makedirs(os.path.dirname(resource_path), exist_ok=True) if not os.path.exists(resource_path): @@ -1387,7 +1408,7 @@ class Drawing(blenderbim.core.tool.Drawing): clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW") - def clone(src): + def clone(src: bpy.types.Object) -> bpy.types.Object: dst = src.copy() dst.data = dst.data.copy() dst.name = dst.name.replace("IfcGridAxis/", "") @@ -1395,13 +1416,13 @@ class Drawing(blenderbim.core.tool.Drawing): dst.data.BIMMeshProperties.ifc_definition_id = 0 return dst - def disassemble(obj): + def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]: mesh = bmesh.new() mesh.verts.ensure_lookup_table() mesh.from_mesh(obj.data) return obj, mesh - def assemble(obj, mesh): + def assemble(obj: bpy.types.Object, mesh: bmesh.types.BMesh) -> bpy.types.Object: mesh.to_mesh(obj.data) return obj @@ -1413,7 +1434,7 @@ class Drawing(blenderbim.core.tool.Drawing): obj.matrix_world.translation += annotation_offset return obj, mesh - def clip_to_camera_boundary(mesh): + def clip_to_camera_boundary(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh: mesh.verts.ensure_lookup_table() points = [v.co for v in mesh.verts[0:2]] points = helper.clip_segment(bounds, points) @@ -1423,7 +1444,7 @@ class Drawing(blenderbim.core.tool.Drawing): mesh.verts[1].co = points[1] return mesh - def draw_grids_vertically(mesh): + def draw_grids_vertically(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh: mesh.verts.ensure_lookup_table() points = [v.co for v in mesh.verts[0:2]] points = helper.elevate_segment(bounds, points) @@ -1534,11 +1555,11 @@ class Drawing(blenderbim.core.tool.Drawing): return text @classmethod - def sync_object_representation(cls, obj): + def sync_object_representation(cls, obj: bpy.types.Object) -> None: bpy.ops.bim.update_representation(obj=obj.name) @classmethod - def sync_object_placement(cls, obj): + def sync_object_placement(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: blender_matrix = np.array(obj.matrix_world) element = tool.Ifc.get_entity(obj) if (obj.scale - mathutils.Vector((1.0, 1.0, 1.0))).length > 1e-4: @@ -1552,7 +1573,7 @@ class Drawing(blenderbim.core.tool.Drawing): return element @classmethod - def sync_grid_axis_object_placement(cls, obj, element): + def sync_grid_axis_object_placement(cls, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None: grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0] grid_obj = tool.Ifc.get_object(grid) if grid_obj: @@ -1568,11 +1589,11 @@ class Drawing(blenderbim.core.tool.Drawing): return document.HasDocumentReferences or [] @classmethod - def get_references_with_location(cls, location): + def get_references_with_location(cls, location: Union[str, None]) -> list[ifcopenshell.entity_instance]: return [r for r in tool.Ifc.get().by_type("IfcDocumentReference") if r.Location == location] @classmethod - def update_embedded_svg_location(cls, uri, reference, new_location): + def update_embedded_svg_location(cls, uri: str, reference: ifcopenshell.entity_instance, new_location: str) -> None: tree = etree.parse(uri) root = tree.getroot() rel_location = os.path.relpath(new_location, os.path.dirname(uri)) @@ -1612,11 +1633,13 @@ class Drawing(blenderbim.core.tool.Drawing): return attributes @classmethod - def get_reference_location(cls, reference): + def get_reference_location(cls, reference: ifcopenshell.entity_instance) -> Union[str, None]: return reference.Location @classmethod - def get_reference_element(cls, reference): + def get_reference_element( + cls, reference: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: if tool.Ifc.get_schema() == "IFC2X3": refs = [r for r in tool.Ifc.get().by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference] else: @@ -1625,12 +1648,12 @@ class Drawing(blenderbim.core.tool.Drawing): return refs[0].RelatedObjects[0] @classmethod - def get_drawing_human_scale(cls, drawing): + def get_drawing_human_scale(cls, drawing: ifcopenshell.entity_instance) -> str: pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") or {} return "NTS" if pset.get("IsNTS", False) else pset.get("HumanScale", "NTS") @classmethod - def get_drawing_metadata(cls, drawing): + def get_drawing_metadata(cls, drawing: ifcopenshell.entity_instance) -> list[str]: # fmt: off return [ v.strip() @@ -1642,7 +1665,7 @@ class Drawing(blenderbim.core.tool.Drawing): # fmt: on @classmethod - def get_annotation_z_index(cls, drawing): + def get_annotation_z_index(cls, drawing: ifcopenshell.entity_instance) -> float: return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0 @classmethod @@ -1654,11 +1677,11 @@ class Drawing(blenderbim.core.tool.Drawing): return symbol @classmethod - def has_linework(cls, drawing): + def has_linework(cls, drawing: ifcopenshell.entity_instance) -> bool: return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasLinework", False) @classmethod - def has_annotation(cls, drawing): + def has_annotation(cls, drawing: ifcopenshell.entity_instance) -> bool: return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False) @classmethod @@ -1723,19 +1746,21 @@ class Drawing(blenderbim.core.tool.Drawing): return elements @classmethod - def get_annotation_element(cls, element): + def get_annotation_element(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: for rel in element.HasAssignments: if rel.is_a("IfcRelAssignsToProduct"): return rel.RelatingProduct @classmethod - def get_drawing_reference(cls, drawing): + def get_drawing_reference(cls, drawing: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: for rel in drawing.HasAssociations: if rel.is_a("IfcRelAssociatesDocument"): return rel.RelatingDocument @classmethod - def get_reference_document(cls, reference): + def get_reference_document( + cls, reference: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: if tool.Ifc.get_schema() == "IFC2X3": return reference.ReferenceToDocument[0] return reference.ReferencedDocument @@ -1749,13 +1774,13 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Ifc.get_object(product).select_set(True) @classmethod - def is_drawing_active(cls): + def is_drawing_active(cls) -> bool: camera = bpy.context.scene.camera area = tool.Blender.get_view3d_area() return camera and camera.type == "CAMERA" and camera.BIMObjectProperties.ifc_definition_id and area @classmethod - def is_camera_orthographic(cls): + def is_camera_orthographic(cls) -> bool: camera = bpy.context.scene.camera return True if (camera and camera.data.type == "ORTHO") else False @@ -1764,7 +1789,7 @@ class Drawing(blenderbim.core.tool.Drawing): return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id @classmethod - def run_drawing_activate_model(cls): + def run_drawing_activate_model(cls) -> None: bpy.ops.bim.activate_model() @classmethod @@ -1906,7 +1931,15 @@ class Drawing(blenderbim.core.tool.Drawing): ) @classmethod - def is_in_camera_view(cls, obj, camera_inverse_matrix, x, y, clip_start, clip_end): + def is_in_camera_view( + cls, + obj: bpy.types.Object, + camera_inverse_matrix: Matrix, + x: float, + y: float, + clip_start: float, + clip_end: float, + ) -> bool: local_bbox = [camera_inverse_matrix @ obj.matrix_world @ Vector(v) for v in obj.bound_box] local_x = [v.x for v in local_bbox] local_y = [v.y for v in local_bbox] @@ -1921,14 +1954,14 @@ class Drawing(blenderbim.core.tool.Drawing): return True @classmethod - def is_intersecting_camera(cls, obj, camera): + def is_intersecting_camera(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> bool: # Based on separating axis theorem plane_co = camera.matrix_world.translation plane_no = camera.matrix_world.col[2].xyz return cls.is_intersecting_plane(obj, plane_co, plane_no) @classmethod - def is_intersecting_plane(cls, obj, plane_co, plane_no): + def is_intersecting_plane(cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector) -> bool: # Broadphase check using the bounding box bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box] bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords] @@ -1958,7 +1991,7 @@ class Drawing(blenderbim.core.tool.Drawing): return pos_exists and neg_exists @classmethod - def bisect_mesh(cls, obj, camera): + def bisect_mesh(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> tuple[list[Vector], list[list[int]]]: camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world plane_co = camera_matrix.translation plane_no = camera_matrix.col[2].xyz @@ -1969,7 +2002,9 @@ class Drawing(blenderbim.core.tool.Drawing): return cls.bisect_mesh_with_plane(obj, plane_co, plane_no, global_offset=global_offset) @classmethod - def bisect_mesh_with_plane(cls, obj, plane_co, plane_no, global_offset=None): + def bisect_mesh_with_plane( + cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector, global_offset: Optional[Vector] = None + ) -> tuple[list[Vector], list[list[int]]]: if global_offset is None: global_offset = Vector() @@ -1980,9 +2015,9 @@ class Drawing(blenderbim.core.tool.Drawing): geom = bm.verts[:] + bm.edges[:] + bm.faces[:] results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no) - vert_map = {} - verts = [] - edges = [] + vert_map: dict[int, int] = {} + verts: list[Vector] = [] + edges: list[list[int]] = [] i = 0 for geom in results["geom_cut"]: if isinstance(geom, bmesh.types.BMVert): @@ -1998,12 +2033,12 @@ class Drawing(blenderbim.core.tool.Drawing): return verts, edges @classmethod - def get_scale_ratio(cls, scale): + def get_scale_ratio(cls, scale: str) -> float: numerator, denominator = scale.split("/") return float(numerator) / float(denominator) @classmethod - def get_diagram_scale(cls, obj): + def get_diagram_scale(cls, obj: bpy.types.Object) -> dict[str, float]: props = obj.data.BIMCameraProperties scale = props.diagram_scale if scale != "CUSTOM": @@ -2029,7 +2064,7 @@ class Drawing(blenderbim.core.tool.Drawing): return {"HumanScale": human_scale, "Scale": scale} @classmethod - def convert_scale_string(cls, value): + def convert_scale_string(cls, value: str) -> float: try: return float(value) except: @@ -2060,7 +2095,7 @@ class Drawing(blenderbim.core.tool.Drawing): return result * 0.0254 @classmethod - def extend_line(cls, start, end, distance): + def extend_line(cls, start: Vector, end: Vector, distance: float) -> tuple[list[float], list[float]]: start = np.array(start) end = np.array(end) direction = end - start @@ -2068,8 +2103,8 @@ class Drawing(blenderbim.core.tool.Drawing): return (start - offset).tolist(), (end + offset).tolist() @classmethod - def get_sheet_references(cls, drawing): - sheet_references = [] + def get_sheet_references(cls, drawing: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: + sheet_references: list[ifcopenshell.entity_instance] = [] drawing_reference = cls.get_drawing_document(drawing) for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"): if not sheet.Scope == "SHEET": @@ -2082,7 +2117,7 @@ class Drawing(blenderbim.core.tool.Drawing): return sheet_references @classmethod - def get_camera_matrix(cls, camera): + def get_camera_matrix(cls, camera: bpy.types.Object) -> Matrix: matrix_world = camera.matrix_world.copy().normalized() location, rotation, scale = matrix_world.decompose() if scale.x < 0 or scale.y < 0 or scale.z < 0: diff --git a/src/blenderbim/blenderbim/tool/georeference.py b/src/blenderbim/blenderbim/tool/georeference.py index 3c9bb0eea1..577a173259 100644 --- a/src/blenderbim/blenderbim/tool/georeference.py +++ b/src/blenderbim/blenderbim/tool/georeference.py @@ -21,6 +21,9 @@ import json import numpy as np import ifcopenshell import ifcopenshell.api.georeference +import ifcopenshell.util.geolocation +import ifcopenshell.util.placement +import ifcopenshell.util.unit import blenderbim.core.tool import blenderbim.tool as tool import blenderbim.bim.helper diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index f1a4eb86c3..81b397964a 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -1,3 +1,22 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +from __future__ import annotations import os import re import bpy @@ -5,6 +24,7 @@ import logging from blenderbim.bim import import_ifc from blenderbim.bim.ifc import IfcStore import blenderbim.tool as tool +from typing import TYPE_CHECKING, Union # allows git import even if git executable isn't found os.environ["GIT_PYTHON_REFRESH"] = "quiet" @@ -13,15 +33,20 @@ try: except ImportError: print("Warning: GitPython not available.") +if TYPE_CHECKING: + import git + class IfcGit: + STEP_IDS = dict[str, set[int]] + @classmethod - def init_repo(cls, path_dir): + def init_repo(cls, path_dir: str) -> None: IfcGitRepo.repo = git.Repo.init(path_dir) cls.config_info_attributes(IfcGitRepo.repo) @classmethod - def clone_repo(cls, remote_url, local_folder): + def clone_repo(cls, remote_url: str, local_folder: str) -> git.Repo: IfcGitRepo.repo = git.Repo.clone_from( url=remote_url, to_path=local_folder, @@ -30,7 +55,7 @@ class IfcGit: return IfcGitRepo.repo @classmethod - def load_anyifc(cls, repo): + def load_anyifc(cls, repo: git.Repo) -> bool: working_dir = repo.working_dir for item in os.listdir(working_dir): path = os.path.join(working_dir, item) @@ -40,11 +65,11 @@ class IfcGit: return False @classmethod - def get_path_dir(cls, path_ifc): + def get_path_dir(cls, path_ifc: str) -> str: return os.path.abspath(os.path.dirname(path_ifc)) @classmethod - def repo_from_path(cls, path): + def repo_from_path(cls, path: str) -> Union[git.Repo, None]: """Returns a Git repository object or None""" if os.path.isdir(path): @@ -72,7 +97,7 @@ class IfcGit: return repo @classmethod - def add_file_to_repo(cls, repo, path_file): + def add_file_to_repo(cls, repo: git.Repo, path_file: str) -> None: if os.name == "nt": cls.dos2unix(path_file) repo.index.add(path_file) @@ -80,11 +105,11 @@ class IfcGit: bpy.ops.ifcgit.refresh() @classmethod - def git_checkout(cls, path_file): + def git_checkout(cls, path_file: str) -> None: IfcGitRepo.repo.git.checkout(path_file) @classmethod - def checkout_new_branch(cls, path_file): + def checkout_new_branch(cls, path_file: str) -> None: """Create a branch and move uncommitted changes to this branch""" props = bpy.context.scene.IfcGitProperties if props.new_branch_name: @@ -94,7 +119,7 @@ class IfcGit: bpy.ops.ifcgit.refresh() @classmethod - def git_commit(cls, path_file): + def git_commit(cls, path_file: str) -> None: props = bpy.context.scene.IfcGitProperties repo = IfcGitRepo.repo if os.name == "nt": @@ -104,7 +129,7 @@ class IfcGit: props.commit_message = "" @classmethod - def add_tag(cls, repo): + def add_tag(cls, repo: git.Repo) -> None: props = bpy.context.scene.IfcGitProperties item = props.ifcgit_commits[props.commit_index] repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message) @@ -112,19 +137,19 @@ class IfcGit: props.new_tag_message = "" @classmethod - def delete_tag(cls, repo, tag_name): + def delete_tag(cls, repo: git.Repo, tag_name: git.TagReference) -> None: if tag_name in repo.tags: repo.delete_tag(tag_name) @classmethod - def add_remote(cls, repo): + def add_remote(cls, repo: git.Repo) -> None: props = bpy.context.scene.IfcGitProperties repo.create_remote(name=props.remote_name, url=props.remote_url) props.remote_name = "" props.remote_url = "" @classmethod - def delete_remote(cls, repo): + def delete_remote(cls, repo: git.Repo) -> None: props = bpy.context.scene.IfcGitProperties remote_name = props.select_remote if remote_name in repo.remotes: @@ -133,7 +158,7 @@ class IfcGit: props.select_remote = repo.remotes[0].name @classmethod - def push(cls, repo, remote_name, branch_name): + def push(cls, repo: git.Repo, remote_name: str, branch_name: str) -> Union[str, None]: cls.config_push(repo) remote = repo.remotes[remote_name] try: @@ -142,7 +167,7 @@ class IfcGit: return exc.stderr @classmethod - def create_new_branch(cls): + def create_new_branch(cls) -> None: """Convert a detached HEAD into a branch""" props = bpy.context.scene.IfcGitProperties repo = IfcGitRepo.repo @@ -154,7 +179,7 @@ class IfcGit: bpy.ops.ifcgit.refresh() @classmethod - def clear_commits_list(cls): + def clear_commits_list(cls) -> None: area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area.spaces[0].shading.color_type = "MATERIAL" props = bpy.context.scene.IfcGitProperties @@ -163,7 +188,7 @@ class IfcGit: props.ifcgit_commits.clear() @classmethod - def get_commits_list(cls, path_ifc, lookup): + def get_commits_list(cls, path_ifc: str, lookup: dict[str, Any]) -> None: props = bpy.context.scene.IfcGitProperties repo = cls.repo_from_path(path_ifc) @@ -205,14 +230,14 @@ class IfcGit: list_item.tags[-1].message = tag.tag.message @classmethod - def refresh_revision_list(cls, path_ifc): + def refresh_revision_list(cls, path_ifc: str) -> None: repo = cls.repo_from_path(path_ifc) cls.clear_commits_list() lookup = cls.tags_by_hexsha(repo) cls.get_commits_list(path_ifc, lookup) @classmethod - def is_valid_ref_format(cls, string): + def is_valid_ref_format(cls, string: str) -> Union[re.Match[str], None]: """Check a bare branch or tag name is valid""" return re.match( @@ -221,7 +246,7 @@ class IfcGit: ) @classmethod - def load_project(cls, path_ifc=""): + def load_project(cls, path_ifc: str = "") -> None: """Clear and load an ifc project""" if path_ifc: @@ -248,7 +273,7 @@ class IfcGit: bpy.ops.object.select_all(action="DESELECT") @classmethod - def branches_by_hexsha(cls, repo): + def branches_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]: """reverse lookup for branches""" result = {} @@ -267,7 +292,7 @@ class IfcGit: return result @classmethod - def tags_by_hexsha(cls, repo): + def tags_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]: """reverse lookup for tags""" result = {} @@ -279,7 +304,7 @@ class IfcGit: return result @classmethod - def ifc_diff_ids(cls, repo, hash_a, hash_b, path_ifc): + def ifc_diff_ids(cls, repo: git.Repo, hash_a: str, hash_b: str, path_ifc: str) -> STEP_IDS: """Given two revision hashes and a filename, retrieve""" """step-ids of modified, added and removed entities""" @@ -309,7 +334,7 @@ class IfcGit: } @classmethod - def get_revisions_step_ids(cls): + def get_revisions_step_ids(cls) -> Union[STEP_IDS, None]: path_ifc = bpy.data.scenes["Scene"].BIMProperties.ifc_file props = bpy.context.scene.IfcGitProperties @@ -341,7 +366,7 @@ class IfcGit: return step_ids @classmethod - def get_modified_shape_object_step_ids(cls, step_ids): + def get_modified_shape_object_step_ids(cls, step_ids: STEP_IDS) -> STEP_IDS: model = tool.Ifc.get() modified_shape_object_step_ids = {"modified": []} @@ -353,7 +378,7 @@ class IfcGit: return modified_shape_object_step_ids @classmethod - def update_step_ids(cls, step_ids, modified_shape_object_step_ids): + def update_step_ids(cls, step_ids: STEP_IDS, modified_shape_object_step_ids: STEP_IDS) -> STEP_IDS: final_step_ids = {} final_step_ids["added"] = step_ids["added"] @@ -362,7 +387,7 @@ class IfcGit: return final_step_ids @classmethod - def colourise(cls, step_ids): + def colourise(cls, step_ids: STEP_IDS) -> None: area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area.spaces[0].shading.color_type = "OBJECT" bpy.ops.object.select_all(action="DESELECT") @@ -384,7 +409,7 @@ class IfcGit: obj.color = (1.0, 1.0, 1.0, 0.5) @classmethod - def switch_to_revision_item(cls): + def switch_to_revision_item(cls) -> None: props = bpy.context.scene.IfcGitProperties repo = IfcGitRepo.repo item = props.ifcgit_commits[props.commit_index] @@ -399,13 +424,13 @@ class IfcGit: repo.git.checkout(item.hexsha) @classmethod - def delete_collection(cls, blender_collection): + def delete_collection(cls, blender_collection: bpy.types.Collection) -> None: for obj in blender_collection.objects: bpy.data.objects.remove(obj, do_unlink=True) bpy.data.collections.remove(blender_collection) @classmethod - def is_valid_branch_name(cls, new_branch_name): + def is_valid_branch_name(cls, new_branch_name: str): """Check if a branch name is valid and doesn't conflict with existing branches""" if not cls.is_valid_ref_format(new_branch_name): return False @@ -414,7 +439,7 @@ class IfcGit: return True @classmethod - def config_ifcmerge(cls): + def config_ifcmerge(cls) -> None: config_reader = IfcGitRepo.repo.config_reader() section = 'mergetool "ifcmerge"' if not config_reader.has_section(section): @@ -428,7 +453,7 @@ class IfcGit: config_writer.set_value(section, "trustExitCode", True) @classmethod - def config_push(cls, repo): + def config_push(cls, repo: git.Repo) -> None: """Set push.autoSetupRemote""" config_reader = repo.config_reader() if not config_reader.has_section("push"): @@ -437,7 +462,7 @@ class IfcGit: config_writer.set_value("push", "autoSetupRemote", True) @classmethod - def config_info_attributes(cls, repo): + def config_info_attributes(cls, repo: git.Repo) -> None: """Set IFC files as text in .git/info/attributes""" path_attributes = os.path.join(repo.git_dir, "info", "attributes") if not os.path.exists(path_attributes): @@ -446,7 +471,7 @@ class IfcGit: f.write("*.ifc text") @classmethod - def dos2unix(cls, path_file): + def dos2unix(cls, path_file: str) -> None: with open(path_file, "rb") as infile: content = infile.read() with open(path_file, "wb") as output: @@ -454,7 +479,7 @@ class IfcGit: output.write(line + b"\n") @classmethod - def execute_merge(cls, path_ifc, operator): + def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, False]: props = bpy.context.scene.IfcGitProperties repo = IfcGitRepo.repo item = props.ifcgit_commits[props.commit_index] @@ -498,7 +523,7 @@ class IfcGit: cls.refresh_revision_list(path_ifc) @classmethod - def entity_log(cls, path_ifc, step_id): + def entity_log(cls, path_ifc: str, step_id: int) -> str: """Raw git log for this entity""" repo = IfcGitRepo.repo if not repo: @@ -514,4 +539,4 @@ class IfcGit: class IfcGitRepo: - repo = None + repo: git.Repo = None diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 21b417acdb..f7bffc8fbd 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -392,10 +392,11 @@ class Spatial(blenderbim.core.tool.Spatial): continue old_mesh = obj.data + assert isinstance(old_mesh, bpy.types.Mesh) if visible_element.HasOpenings: new_mesh = cls.get_gross_mesh_from_element(visible_element) else: - new_mesh = obj.data.copy() + new_mesh = old_mesh.copy() obj.data = new_mesh # Boundary objects are likely triangulated. If a triangulated quad @@ -463,15 +464,15 @@ class Spatial(blenderbim.core.tool.Spatial): return mesh @classmethod - def get_x_y_z_h_mat_from_active_obj(cls, active_obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]: - mat = active_obj.matrix_world - local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector()) + def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]: + mat = obj.matrix_world + local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) global_bbox_center = mat @ local_bbox_center x = global_bbox_center.x y = global_bbox_center.y - z = (mat @ Vector(active_obj.bound_box[0])).z + z = (mat @ Vector(obj.bound_box[0])).z - h = active_obj.dimensions.z + h = obj.dimensions.z return x, y, z, h, mat @classmethod @@ -490,7 +491,11 @@ class Spatial(blenderbim.core.tool.Spatial): boundary_elements = cls.get_boundary_elements(selected_objects) polys = cls.get_polygons(boundary_elements) converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) - union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2) + union = shapely.ops.unary_union(polys).buffer( + converted_tolerance, + cap_style=shapely.constructive.BufferCapStyle.flat, + join_style=shapely.constructive.BufferJoinStyle.mitre, + ) union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=0.1)) return union @@ -576,7 +581,12 @@ class Spatial(blenderbim.core.tool.Spatial): def get_buffered_poly_from_linear_ring(cls, linear_ring: shapely.LinearRing) -> Polygon: poly = Polygon(linear_ring) converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) - poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2) + poly = poly.buffer( + converted_tolerance, + single_sided=True, + cap_style=shapely.BufferCapStyle.flat, + join_style=shapely.BufferJoinStyle.mitre, + ) return poly @classmethod diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py index bae2c32ea8..fcf8799707 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py @@ -17,14 +17,15 @@ # along with IfcOpenShell. If not, see . import ifcopenshell -from typing import Optional, Literal +import ifcopenshell.util.representation +from typing import Optional def add_context( file: ifcopenshell.file, - context_type: Optional[Literal["Model", "Plan"]] = None, - context_identifier: Optional[str] = None, - target_view: Optional[str] = None, + context_type: Optional[ifcopenshell.util.representation.CONTEXT_TYPE] = None, + context_identifier: Optional[ifcopenshell.util.representation.REPRESENTATION_IDENTIFIER] = None, + target_view: Optional[ifcopenshell.util.representation.TARGET_VIEW] = None, parent: Optional[ifcopenshell.entity_instance] = None, ) -> ifcopenshell.entity_instance: """Adds a new geometric representation context @@ -106,7 +107,6 @@ def add_context( the common target views above or consult the IFC documentation (under the IfcShapeRepresentation page) for more details. Optional for contexts, but mandatory for subcontexts. - :type target_view: str, optional :param parent: the parent context. Must be left as None (the default) for contexts, and only set for subcontexts. Note that there are only contexts and subcontexts, a subcontext cannot have any children. diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 19eae9790e..f5cb2902b0 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -26,6 +26,7 @@ import typing import inspect import collections import importlib +import importlib.util from typing import Union diff --git a/src/ifcpatch/test/test_MergeProject.py b/src/ifcpatch/test/test_MergeProject.py index c190a4d05a..3c4e84c4f2 100644 --- a/src/ifcpatch/test/test_MergeProject.py +++ b/src/ifcpatch/test/test_MergeProject.py @@ -18,8 +18,14 @@ import ifcpatch import ifcopenshell +import ifcopenshell.api.context +import ifcopenshell.api.geometry import ifcopenshell.api.georeference +import ifcopenshell.geom +import ifcopenshell.util.geolocation import ifcopenshell.util.placement +import ifcopenshell.util.representation +import ifcopenshell.util.shape import ifcopenshell.util.shape_builder import test.bootstrap import tempfile