From adfe56c77794f6421f9ae0f6c0a5b78d6fd23373 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 21 Jul 2025 11:26:15 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/project/operator.py | 12 ++--- src/bonsai/bonsai/bim/module/tester/data.py | 4 +- .../bonsai/bim/module/tester/operator.py | 10 ++-- src/bonsai/bonsai/bim/module/tester/prop.py | 30 +++++++++++- src/bonsai/bonsai/bim/module/tester/ui.py | 49 +++++++++++++++---- src/bonsai/bonsai/tool/tester.py | 14 +++++- 6 files changed, 94 insertions(+), 25 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 60f5171797..1851ab7269 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -1886,12 +1886,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): print("Finished", time.time() - start) return {"FINISHED"} - def process_occurrence(self, shape: ShapeElementType) -> None: + def process_occurrence(self, shape: W.TriangulationElement) -> None: element = self.file.by_id(shape.id) - faces: tuple[int, ...] = shape.geometry.faces - verts: tuple[float, ...] = shape.geometry.verts - materials: tuple[W.style, ...] = shape.geometry.materials - material_ids: tuple[int, ...] = shape.geometry.material_ids + faces = shape.geometry.faces + verts = shape.geometry.verts + materials = shape.geometry.materials + material_ids = shape.geometry.material_ids mat = ifcopenshell.util.shape.get_shape_matrix(shape) @@ -1920,7 +1920,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): verts = geometry.verts mesh["has_cartesian_point_offset"] = False - material_to_slot = {} + material_to_slot: dict[int, int] = {} max_slot_index = 0 for i, material in enumerate(materials): diff --git a/src/bonsai/bonsai/bim/module/tester/data.py b/src/bonsai/bonsai/bim/module/tester/data.py index 4538475d79..d845c210f9 100644 --- a/src/bonsai/bonsai/bim/module/tester/data.py +++ b/src/bonsai/bonsai/bim/module/tester/data.py @@ -39,8 +39,8 @@ class TesterData: return bool(tool.Tester.report) @classmethod - def specification(cls) -> list[ifctester.reporter.ResultsSpecification]: + def specification(cls) -> ifctester.reporter.ResultsSpecification: if not tool.Tester.report: return [] - props = bpy.context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() return tool.Tester.report[props.active_specification_index] diff --git a/src/bonsai/bonsai/bim/module/tester/operator.py b/src/bonsai/bonsai/bim/module/tester/operator.py index 45a8090ba5..e6bf4ad293 100644 --- a/src/bonsai/bonsai/bim/module/tester/operator.py +++ b/src/bonsai/bonsai/bim/module/tester/operator.py @@ -39,7 +39,7 @@ class ExecuteIfcTester(bpy.types.Operator): @classmethod def poll(cls, context): - props = context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() if not props.should_load_from_memory and not props.ifc_files.single_file: cls.poll_message_set("Select an IFC file or use 'load from memory' if it's loaded in Bonsai.") return False @@ -48,7 +48,7 @@ class ExecuteIfcTester(bpy.types.Operator): return True def execute(self, context): - props = context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() props.specifications.clear() @@ -69,7 +69,7 @@ class ExecuteIfcTester(bpy.types.Operator): return {"FINISHED"} def execute_tester(self, ifc_data: ifcopenshell.file, ifc_path: str, specs_path: str) -> Union[set[str], None]: - props = bpy.context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() props.failed_entities.clear() # No need for if-statement, just postponing lots of diffs. @@ -132,7 +132,7 @@ class SelectRequirement(bpy.types.Operator): req_index: bpy.props.IntProperty() def execute(self, context): - props = context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() report = tool.Tester.report props.old_index = self.spec_index failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"] @@ -169,7 +169,7 @@ class SelectFailedEntities(bpy.types.Operator): req_index: bpy.props.IntProperty() def execute(self, context): - props = context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() report = tool.Tester.report props.old_index = self.spec_index failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"] diff --git a/src/bonsai/bonsai/bim/module/tester/prop.py b/src/bonsai/bonsai/bim/module/tester/prop.py index 8b94a08252..d4e53cf258 100644 --- a/src/bonsai/bonsai/bim/module/tester/prop.py +++ b/src/bonsai/bonsai/bim/module/tester/prop.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from typing import TYPE_CHECKING + +import bpy from bonsai.bim.module.tester.data import TesterData from bonsai.bim.prop import StrProperty, MultipleFileSelect from bpy.types import PropertyGroup @@ -31,21 +34,29 @@ from bpy.props import ( ) -def update_active_specification_index(self, context): +def update_active_specification_index(self: "IfcTesterProperties", context: bpy.types.Context) -> None: TesterData.load() class Specification(PropertyGroup): - name: StringProperty(name="Name") description: StringProperty(name="Description") status: BoolProperty(default=False, name="Status") + if TYPE_CHECKING: + description: str + status: bool + class FailedEntities(PropertyGroup): ifc_id: IntProperty(name="IFC ID") element: StringProperty(name="Element") reason: StringProperty(name="Reason") + if TYPE_CHECKING: + ifc_id: int + element: str + reason: str + class IfcTesterProperties(PropertyGroup): specs: PointerProperty(type=MultipleFileSelect) @@ -66,3 +77,18 @@ class IfcTesterProperties(PropertyGroup): failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities) has_entities: BoolProperty(default=False, name="") n_entities: IntProperty(name="", default=0) + + if TYPE_CHECKING: + specs: MultipleFileSelect + ifc_files: MultipleFileSelect + should_load_from_memory: bool + generate_html_report: bool + generate_ods_report: bool + flag: bool + active_specification_index: int + old_index: int + active_failed_entity_index: int + specifications: bpy.types.bpy_prop_collection_idprop[Specification] + failed_entities: bpy.types.bpy_prop_collection_idprop[FailedEntities] + has_entities: bool + n_entities: int diff --git a/src/bonsai/bonsai/bim/module/tester/ui.py b/src/bonsai/bonsai/bim/module/tester/ui.py index 86e80ac981..8953035ab8 100644 --- a/src/bonsai/bonsai/bim/module/tester/ui.py +++ b/src/bonsai/bonsai/bim/module/tester/ui.py @@ -16,10 +16,22 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -import bonsai.tool as tool +from __future__ import annotations + +from typing import TYPE_CHECKING + +import bpy from bpy.types import Panel, UIList +import bonsai.tool as tool from bonsai.bim.module.tester.data import TesterData +if TYPE_CHECKING: + from bonsai.bim.module.tester.prop import ( + FailedEntities, + IfcTesterProperties, + Specification, + ) + class BIM_PT_tester(Panel): bl_label = "IFC Tester" @@ -34,8 +46,9 @@ class BIM_PT_tester(Panel): if not TesterData.is_loaded: TesterData.load() + assert self.layout self.layout.use_property_split = True - props = context.scene.IfcTesterProperties + props = tool.Tester.get_tester_props() if tool.Ifc.get(): row = self.layout.row() @@ -68,16 +81,17 @@ class BIM_PT_tester(Panel): "active_specification_index", ) - self.draw_editable_ui(context) + self.draw_editable_ui() row = self.layout.row() row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT") - def draw_editable_ui(self, context): - props = context.scene.IfcTesterProperties + def draw_editable_ui(self) -> None: + props = tool.Tester.get_tester_props() specification = TesterData.data["specification"] n_requirements = len(specification["requirements"]) + assert self.layout row = self.layout.row() row.label( text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)' @@ -114,7 +128,16 @@ class BIM_PT_tester(Panel): class BIM_UL_tester_specifications(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context: bpy.types.Context, + layout: bpy.types.UILayout, + data: IfcTesterProperties, + item: Specification, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.split(factor=0.3, align=True) row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL") @@ -122,12 +145,20 @@ class BIM_UL_tester_specifications(UIList): class BIM_UL_tester_failed_entities(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = context.scene.IfcTesterProperties + def draw_item( + self, + context: bpy.types.Context, + layout: bpy.types.UILayout, + data: IfcTesterProperties, + item: FailedEntities, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) row.label(text=item.element) row.label(text=item.reason) - if props.should_load_from_memory: + if data.should_load_from_memory: op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF") op.ifc_id = item.ifc_id diff --git a/src/bonsai/bonsai/tool/tester.py b/src/bonsai/bonsai/tool/tester.py index 2d2992444a..b97b4f6cb4 100644 --- a/src/bonsai/bonsai/tool/tester.py +++ b/src/bonsai/bonsai/tool/tester.py @@ -16,11 +16,23 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations + +from typing import TYPE_CHECKING, Union + +import bpy import ifctester.ids import ifctester.reporter -from typing import Union + +if TYPE_CHECKING: + from bonsai.bim.module.tester.prop import IfcTesterProperties class Tester: specs: Union[ifctester.ids.Ids, None] = None report: list[ifctester.reporter.ResultsSpecification] = [] + + @classmethod + def get_tester_props(cls) -> IfcTesterProperties: + assert (scene := bpy.context.scene) + return scene.IfcTesterProperties # pyright: ignore[reportAttributeAccessIssue]