From 9282ccabb30a6e0ced2903eeabfd3a52925bd883 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 17 Jun 2025 11:46:15 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/bim/handler.py | 6 +-- src/bonsai/bonsai/bim/helper.py | 9 +++-- .../bonsai/bim/module/debug/operator.py | 16 ++++++-- src/bonsai/bonsai/bim/module/debug/ui.py | 1 + src/bonsai/bonsai/core/debug.py | 6 +-- src/bonsai/bonsai/core/profile.py | 2 +- src/bonsai/bonsai/core/style.py | 38 ++++++++++++------- src/bonsai/bonsai/core/type.py | 7 +++- .../ifcopenshell/entity_instance.py | 9 +++-- .../ifcopenshell/ifcopenshell_wrapper.pyi | 4 ++ .../ifcopenshell/util/cost.py | 5 ++- .../ifcopenshell/util/schema.py | 6 +-- .../ifcopenshell/util/unit.py | 7 ++-- 13 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index 21cfd08adb..2a90ecdcbc 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -277,10 +277,10 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None return pao elif ifc.schema == "IFC2X3": if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None: - person = tool.Ifc.run("owner.add_person") + person = ifcopenshell.api.owner.add_person(ifc) if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None: - organization = tool.Ifc.run("owner.add_organisation") - pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization) + organization = ifcopenshell.api.owner.add_organisation(ifc) + pao = ifcopenshell.api.owner.add_person_and_organisation(ifc, person=person, organisation=organization) return pao diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index 0a7cb1e325..0fbbb94901 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -121,7 +121,8 @@ def import_attributes( callback: Optional[ImportCallback] = None, ) -> None: schema = tool.Ifc.schema() - for attribute in schema.declaration_by_name(ifc_class).all_attributes(): + assert (entity := schema.declaration_by_name(ifc_class).as_entity()) + for attribute in entity.all_attributes(): import_attribute(attribute, props, data, callback=callback) @@ -132,11 +133,13 @@ def import_attributes2( callback: Optional[ImportCallback] = None, ) -> None: if isinstance(element, str): - attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes() + assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity()) + attributes = entity.all_attributes() info = {a.name(): None for a in attributes} info["type"] = element else: - attributes = element.wrapped_data.declaration().as_entity().all_attributes() + assert (entity := element.wrapped_data.declaration().as_entity()) + attributes = entity.all_attributes() info = element.get_info() for attribute in attributes: import_attribute(attribute, props, info, callback=callback) diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py index 8f39bc95ac..af5c841761 100644 --- a/src/bonsai/bonsai/bim/module/debug/operator.py +++ b/src/bonsai/bonsai/bim/module/debug/operator.py @@ -42,7 +42,10 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper from pathlib import Path from bonsai import get_debug_info, format_debug_info from bonsai.bim.ifc import IfcStore -from typing import get_args, Union +from typing import get_args, Union, Any, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.prop import Attribute class CopyDebugInformation(bpy.types.Operator): @@ -70,6 +73,7 @@ class CopyDebugInformation(bpy.types.Operator): print(text_with_backticks) print("-" * 80) + assert context.window_manager context.window_manager.clipboard = text_with_backticks return {"FINISHED"} @@ -347,8 +351,9 @@ class SelectHighPolygonMeshes(bpy.types.Operator): threshold: bpy.props.IntProperty() def execute(self, context): + assert context.view_layer for obj in context.view_layer.objects: - if obj.type == "MESH" and len(obj.data.polygons) > self.threshold: + if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons) > self.threshold: obj.select_set(True) return {"FINISHED"} @@ -361,6 +366,7 @@ class SelectHighestPolygonMeshes(bpy.types.Operator): percentile: bpy.props.IntProperty() def execute(self, context): + assert context.view_layer objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"] if objects: percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100 @@ -423,7 +429,7 @@ class InspectFromStepId(bpy.types.Operator): new.int_value = inverse.id() return {"FINISHED"} - def add_attribute(self, prop, key, value): + def add_attribute(self, prop: "bpy.types.bpy_prop_collection_idprop[Attribute]", key: str, value: Any) -> None: if isinstance(value, tuple) and len(value) < 10: for i, item in enumerate(value): self.add_attribute(prop, key + f"[{i}]", item) @@ -459,8 +465,10 @@ class InspectFromObject(bpy.types.Operator): def poll(cls, context): if not context.active_object: cls.poll_message_set("No Active Object") + return False elif not cls.get_active_object_ifc_definition(context): cls.poll_message_set("Active Object doesn't have an IFC definition") + return False else: return True @@ -492,6 +500,7 @@ class PrintObjectPlacement(bpy.types.Operator): if self.create_empty_object: bpy.ops.object.empty_add(type="ARROWS") si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + assert context.active_object context.active_object.matrix_world = placement.transpose() context.active_object.matrix_world.translation *= si_conversion context.active_object.empty_display_size = self.arrow_size @@ -1017,6 +1026,7 @@ class RestartBlender(bpy.types.Operator): def execute(self, context): # Save preferences manually since we're restarting Blender using .execv # and it doens't have a chance to save them on exit. + assert context.preferences if context.preferences.use_preferences_save: bpy.ops.wm.save_userpref() diff --git a/src/bonsai/bonsai/bim/module/debug/ui.py b/src/bonsai/bonsai/bim/module/debug/ui.py index 12667d5e1a..14e257c40a 100644 --- a/src/bonsai/bonsai/bim/module/debug/ui.py +++ b/src/bonsai/bonsai/bim/module/debug/ui.py @@ -31,6 +31,7 @@ class BIM_PT_debug(Panel): bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): + assert self.layout layout = self.layout props = tool.Debug.get_debug_props() diff --git a/src/bonsai/bonsai/core/debug.py b/src/bonsai/bonsai/core/debug.py index ccf569798c..7975bdfae3 100644 --- a/src/bonsai/bonsai/core/debug.py +++ b/src/bonsai/bonsai/core/debug.py @@ -26,15 +26,15 @@ if TYPE_CHECKING: import bonsai.tool as tool -def parse_express(debug: tool.Debug, filename: str) -> None: +def parse_express(debug: type[tool.Debug], filename: str) -> None: debug.add_schema_identifier(debug.load_express(filename)) -def purge_hdf5_cache(debug: tool.Debug) -> None: +def purge_hdf5_cache(debug: type[tool.Debug]) -> None: debug.purge_hdf5_cache() -def purge_unused_elements(ifc, debug: tool.Debug, ifc_class: str) -> int: +def purge_unused_elements(ifc: type[tool.Ifc], debug: type[tool.Debug], ifc_class: str) -> int: ifc_file = ifc.get() unused_elements = [i for i in ifc_file.by_type(ifc_class) if ifc_file.get_total_inverses(i) == 0] unused_elements_amount = len(unused_elements) diff --git a/src/bonsai/bonsai/core/profile.py b/src/bonsai/bonsai/core/profile.py index a0ee00b6de..46e179229c 100644 --- a/src/bonsai/bonsai/core/profile.py +++ b/src/bonsai/bonsai/core/profile.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: import bonsai.tool as tool -def purge_unused_profiles(ifc: tool.Ifc, profile: tool.Profile) -> int: +def purge_unused_profiles(ifc: type[tool.Ifc], profile: type[tool.Profile]) -> int: """Purge profiles that have no inverses. :return: Number of removed profiles. diff --git a/src/bonsai/bonsai/core/style.py b/src/bonsai/bonsai/core/style.py index 0c68c4373b..85bb4200aa 100644 --- a/src/bonsai/bonsai/core/style.py +++ b/src/bonsai/bonsai/core/style.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: import bonsai.tool as tool -def add_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material) -> ifcopenshell.entity_instance: +def add_style(ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material) -> ifcopenshell.entity_instance: element = ifc.run("style.add_style", name=style.get_name(obj)) ifc.link(element, obj) if style.can_support_rendering_style(obj): @@ -38,7 +38,9 @@ def add_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material) -> ifco # TODO: outdated. -def add_external_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material, attributes: dict[str, Any]) -> None: +def add_external_style( + ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material, attributes: dict[str, Any] +) -> None: element = ifc.get_entity(obj) ifc.run( "style.add_surface_style", style=element, ifc_class="IfcExternallyDefinedSurfaceStyle", attributes=attributes @@ -47,7 +49,7 @@ def add_external_style(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material # TODO: unused `style` argument? def update_external_style( - ifc: tool.Ifc, + ifc: type[tool.Ifc], style: ifcopenshell.entity_instance, external_style: ifcopenshell.entity_instance, attributes: dict[str, Any], @@ -56,7 +58,10 @@ def update_external_style( def remove_style( - ifc: tool.Ifc, style_tool: tool.Style, style: ifcopenshell.entity_instance, reload_styles_ui: bool = False + ifc: type[tool.Ifc], + style_tool: type[tool.Style], + style: ifcopenshell.entity_instance, + reload_styles_ui: bool = False, ) -> None: """Remove IfcPresentationStyle and associated Blender material. @@ -78,7 +83,9 @@ def remove_style( style_tool.import_presentation_styles(style_type) -def update_style_colours(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Material, verbose: bool = False) -> None: +def update_style_colours( + ifc: type[tool.Ifc], style: type[tool.Style], obj: bpy.types.Material, verbose: bool = False +) -> None: element = ifc.get_entity(obj) if style.can_support_rendering_style(obj): @@ -114,7 +121,10 @@ def update_style_colours(ifc: tool.Ifc, style: tool.Style, obj: bpy.types.Materi def update_style_textures( - ifc: tool.Ifc, style: tool.Style, obj: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance + ifc: type[tool.Ifc], + style: type[tool.Style], + obj: ifcopenshell.entity_instance, + representation: ifcopenshell.entity_instance, ) -> None: element = ifc.get_entity(obj) @@ -136,22 +146,22 @@ def update_style_textures( # TODO: outdated. -def unlink_style(ifc: tool.Ifc, style: ifcopenshell.entity_instance) -> None: +def unlink_style(ifc: type[tool.Ifc], style: ifcopenshell.entity_instance) -> None: ifc.unlink(element=style) -def enable_editing_style(style_tool: tool.Style, style: ifcopenshell.entity_instance) -> None: +def enable_editing_style(style_tool: type[tool.Style], style: ifcopenshell.entity_instance) -> None: style_tool.enable_editing(style) style_tool.import_surface_attributes(style) -def disable_editing_style(style: tool.Style) -> None: +def disable_editing_style(style: type[tool.Style]) -> None: obj = style.get_currently_edited_material() style.disable_editing() style.reload_material_from_ifc(obj) -def edit_style(ifc: tool.Ifc, style: tool.Style) -> None: +def edit_style(ifc: type[tool.Ifc], style: type[tool.Style]) -> None: obj = style.get_currently_edited_material() style_element = ifc.get_entity(obj) assert style_element @@ -164,14 +174,16 @@ def edit_style(ifc: tool.Ifc, style: tool.Style) -> None: style.reload_representations(style_element) -def load_styles(style: tool.Style, style_type: str) -> None: +def load_styles(style: type[tool.Style], style_type: str) -> None: style.import_presentation_styles(style_type) style.enable_editing_styles() -def disable_editing_styles(style: tool.Style) -> None: +def disable_editing_styles(style: type[tool.Style]) -> None: style.disable_editing_styles() -def select_by_style(style_tool: tool.Style, spatial: tool.Spatial, style: ifcopenshell.entity_instance) -> None: +def select_by_style( + style_tool: type[tool.Style], spatial: type[tool.Spatial], style: ifcopenshell.entity_instance +) -> None: spatial.select_products(style_tool.get_elements_by_style(style)) diff --git a/src/bonsai/bonsai/core/type.py b/src/bonsai/bonsai/core/type.py index a7c334dfd5..226f00c461 100644 --- a/src/bonsai/bonsai/core/type.py +++ b/src/bonsai/bonsai/core/type.py @@ -27,7 +27,10 @@ if TYPE_CHECKING: def assign_type( - ifc: tool.Ifc, type_tool: tool.Type, element: ifcopenshell.entity_instance, type: ifcopenshell.entity_instance + ifc: type[tool.Ifc], + type_tool: type[tool.Type], + element: ifcopenshell.entity_instance, + type: ifcopenshell.entity_instance, ) -> None: ifc.run("type.assign_type", related_objects=[element], relating_type=type) obj = ifc.get_object(element) @@ -40,7 +43,7 @@ def assign_type( type_tool.disable_editing(obj) -def purge_unused_types(ifc: tool.Ifc, type: tool.Type, geometry: tool.Geometry) -> int: +def purge_unused_types(ifc: type[tool.Ifc], type: type[tool.Type], geometry: type[tool.Geometry]) -> int: """Remove all types without occurrences, return an amount of the removed types.""" purged_types = 0 for element_type in type.get_model_types(): diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py index 53ef66cbd0..0029b75041 100644 --- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py +++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py @@ -26,7 +26,7 @@ import operator import subprocess import sys import time -from typing import Union, Any, TypeVar, overload, TYPE_CHECKING +from typing import Union, Any, TypeVar, overload, TYPE_CHECKING, cast, NoReturn from collections.abc import Callable, Sequence from . import ifcopenshell_wrapper @@ -64,17 +64,20 @@ def set_unsupported_attribute(*args): # done for each invocation of __setitem__. Now this # mapping is built once during initialization of the # module. -_method_dict = {} +MethodList = list[Callable[[ifcopenshell_wrapper.entity_instance, int, Any], Union[None, NoReturn]]] +"""List of setter methods for class attributes.""" +_method_dict: dict[str, MethodList] = {} +"""Mapping of entity classes (e.g. 'IFC4.IfcWall') to MethodLists.""" def register_schema_attributes(schema: ifcopenshell_wrapper.schema_definition) -> None: for decl in schema.declarations(): - decl: ifcopenshell_wrapper.declaration if hasattr(decl, "argument_types"): fq_name = ".".join((schema.name(), decl.name())) # get type strings as reported by IfcOpenShell C++ type_strs = decl.argument_types() + type_strs = cast(Sequence[str], type_strs) # convert case for setter function type_strs = [x.title().replace(" ", "") for x in type_strs] diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi index d0e5c07f4f..eeb3812f78 100644 --- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi +++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . +import ifcopenshell from typing import Any, Union, Literal # `std::vector` usually translated to `tuple[xxx, ...]`. @@ -775,6 +776,9 @@ class entity(declaration): def supertype(self) -> Union[entity, None]: ... class entity_instance: + file: ifcopenshell.file + """Reference to IFC file to prevent it's garbage collection, if entity is still used.""" + file_: Any id_: Any def data(self, *args): ... diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index 3027897520..71af763d48 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -282,9 +282,10 @@ def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, s def get_cost_schedule_types(file: ifcopenshell.file) -> list[dict[str, str]]: - schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(file.schema_identifier) + schema = ifcopenshell_wrapper.schema_by_name(file.schema_identifier) results = [] - declaration = schema.declaration_by_name("IfcCostSchedule") + declaration = schema.declaration_by_name("IfcCostSchedule").as_entity() + assert declaration version = file.schema_identifier for attribute in declaration.attributes(): if attribute.name() == "PredefinedType": diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index b634edab31..698a3a9987 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -136,8 +136,8 @@ def get_subtypes( [, , ..., ] """ - def get_classes(decl): - results = [] + def get_classes(decl: ifcopenshell_wrapper.entity) -> list[ifcopenshell_wrapper.entity]: + results: list[ifcopenshell_wrapper.entity] = [] if not decl.is_abstract(): results.append(decl) for subtype in decl.subtypes(): @@ -172,7 +172,7 @@ def reassign_class( if not ifc_file: ifc_file = element.file - schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier) + schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier) try: declaration = schema.declaration_by_name(new_class) except RuntimeError: diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index fbebb799d0..b7e91c798b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -815,12 +815,13 @@ def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_ None, None, ]: - schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier) + schema = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier) for element in ifc_file: - entity = schema.declaration_by_name(element.is_a()) + entity = schema.declaration_by_name(element.is_a()).as_entity() + assert entity attrs = entity.all_attributes() - attrs_derived: tuple[bool, ...] = entity.derived() + attrs_derived = entity.derived() for attr, val, is_derived in zip(attrs, list(element), attrs_derived): if is_derived: continue