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]