diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py
index b33b0127d5..42e1f55621 100644
--- a/src/bonsai/bonsai/bim/import_ifc.py
+++ b/src/bonsai/bonsai/bim/import_ifc.py
@@ -662,7 +662,7 @@ class IfcImporter:
settings: Optional[ifcopenshell.geom.main.settings] = None,
) -> set[ifcopenshell.entity_instance]:
checkpoint = time.time()
- results = set()
+ results: set[ifcopenshell.entity_instance] = set()
if not products:
return results
diff --git a/src/bonsai/bonsai/bim/module/bsdd/prop.py b/src/bonsai/bonsai/bim/module/bsdd/prop.py
index ea3260909d..caa260cda6 100644
--- a/src/bonsai/bonsai/bim/module/bsdd/prop.py
+++ b/src/bonsai/bonsai/bim/module/bsdd/prop.py
@@ -35,7 +35,7 @@ from bpy.props import (
from typing import Union, TYPE_CHECKING, Literal
-def get_active_dictionary(self, context):
+def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not BSDDData.is_loaded:
BSDDData.load()
return BSDDData.data["active_dictionary"]
diff --git a/src/bonsai/bonsai/bim/module/fm/__init__.py b/src/bonsai/bonsai/bim/module/fm/__init__.py
index 3b351dbe0e..dc26a6379c 100644
--- a/src/bonsai/bonsai/bim/module/fm/__init__.py
+++ b/src/bonsai/bonsai/bim/module/fm/__init__.py
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+# pyright: reportAttributeAccessIssue=false
+
import bpy
from . import ui, prop, operator
diff --git a/src/bonsai/bonsai/bim/module/fm/operator.py b/src/bonsai/bonsai/bim/module/fm/operator.py
index 919fd76821..cda36598db 100644
--- a/src/bonsai/bonsai/bim/module/fm/operator.py
+++ b/src/bonsai/bonsai/bim/module/fm/operator.py
@@ -36,24 +36,25 @@ class ExecuteIfcFM(bpy.types.Operator, ExportHelper):
@property
def filename_ext(self) -> str:
- props = bpy.context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
return f".{props.format}"
def draw(self, context):
+ assert self.layout
layout = self.layout
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
layout.prop(props, "format")
@classmethod
def poll(cls, context):
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_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
return True
def execute(self, context):
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
ifc_file = tool.Ifc.get()
filepaths = []
if ifc_file and props.should_load_from_memory:
@@ -95,7 +96,7 @@ class SelectFMSpreadsheetFiles(bpy.types.Operator, ImportHelper):
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
def execute(self, context):
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
props.spreadsheet_files.clear()
dirname = os.path.dirname(self.filepath)
for f in self.files:
@@ -112,24 +113,24 @@ class ExecuteIfcFMFederate(bpy.types.Operator, ExportHelper):
@property
def filename_ext(self) -> str:
- props = bpy.context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
return f".{props.format}"
def draw(self, context):
layout = self.layout
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
layout.prop(props, "format")
@classmethod
def poll(cls, context):
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
if not props.spreadsheet_files:
cls.poll_message_set("No spreadsheet files selected.")
return False
return True
def execute(self, context):
- props = context.scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
parser = ifcfm.Parser(preset=props.engine)
parser.federate([f.name for f in props.spreadsheet_files])
writer = ifcfm.Writer(parser)
diff --git a/src/bonsai/bonsai/bim/module/fm/prop.py b/src/bonsai/bonsai/bim/module/fm/prop.py
index 2e7c1c469f..25db89224f 100644
--- a/src/bonsai/bonsai/bim/module/fm/prop.py
+++ b/src/bonsai/bonsai/bim/module/fm/prop.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
import bpy
+import bonsai.tool as tool
from bonsai.bim.module.fm.data import FMData
from bonsai.bim.prop import MultipleFileSelect, StrProperty
from bpy.types import PropertyGroup
@@ -30,9 +31,10 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING, Literal
-def get_engine(self, context):
+def get_engine(self: "BIMFMProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not FMData.is_loaded:
FMData.load()
return FMData.data["engine"]
@@ -57,3 +59,10 @@ class BIMFMProperties(PropertyGroup):
default="ods",
options=set(),
)
+
+ if TYPE_CHECKING:
+ ifc_files: MultipleFileSelect
+ spreadsheet_files: bpy.types.bpy_prop_collection_idprop[StrProperty]
+ should_load_from_memory: bool
+ engine: str
+ format: Literal["csv", "xlsx", "ods"]
diff --git a/src/bonsai/bonsai/bim/module/fm/ui.py b/src/bonsai/bonsai/bim/module/fm/ui.py
index 89cd764adf..fa889c61fd 100644
--- a/src/bonsai/bonsai/bim/module/fm/ui.py
+++ b/src/bonsai/bonsai/bim/module/fm/ui.py
@@ -33,11 +33,11 @@ class BIM_PT_fm(Panel):
if not FMData.is_loaded:
FMData.load()
+ assert self.layout
layout = self.layout
layout.use_property_split = True
- scene = context.scene
- props = scene.BIMFMProperties
+ props = tool.Blender.get_fm_props()
if tool.Ifc.get():
row = layout.row()
diff --git a/src/bonsai/bonsai/bim/module/sequence/__init__.py b/src/bonsai/bonsai/bim/module/sequence/__init__.py
index 6f3ef6b52a..0f75c6a1ba 100644
--- a/src/bonsai/bonsai/bim/module/sequence/__init__.py
+++ b/src/bonsai/bonsai/bim/module/sequence/__init__.py
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+# pyright: reportAttributeAccessIssue=false
+
import bpy
from . import ui, prop, operator
diff --git a/src/bonsai/bonsai/bim/module/sequence/data.py b/src/bonsai/bonsai/bim/module/sequence/data.py
index 6fdb0add3f..82caef39fe 100644
--- a/src/bonsai/bonsai/bim/module/sequence/data.py
+++ b/src/bonsai/bonsai/bim/module/sequence/data.py
@@ -367,9 +367,10 @@ class WorkPlansData:
@classmethod
def active_work_plan_schedules(cls):
results = []
- if not bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id:
+ props = tool.Sequence.get_work_plan_props()
+ if not props.active_work_plan_id:
return []
- for rel in tool.Ifc.get().by_id(bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id).IsDecomposedBy:
+ for rel in tool.Ifc.get().by_id(props.active_work_plan_id).IsDecomposedBy:
for work_schedule in rel.RelatedObjects:
results.append({"id": work_schedule.id(), "name": work_schedule.Name or "Unnamed"})
return results
diff --git a/src/bonsai/bonsai/bim/module/sequence/operator.py b/src/bonsai/bonsai/bim/module/sequence/operator.py
index 883142ec8d..25e03dbbac 100644
--- a/src/bonsai/bonsai/bim/module/sequence/operator.py
+++ b/src/bonsai/bonsai/bim/module/sequence/operator.py
@@ -140,10 +140,11 @@ class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Edit Work Plan"
def _execute(self, context):
+ props = tool.Sequence.get_work_plan_props()
core.edit_work_plan(
tool.Ifc,
tool.Sequence,
- work_plan=tool.Ifc.get().by_id(context.scene.BIMWorkPlanProperties.active_work_plan_id),
+ work_plan=tool.Ifc.get().by_id(props.active_work_plan_id),
)
diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py
index ce8bf2a3f3..4c1d1fedda 100644
--- a/src/bonsai/bonsai/bim/module/sequence/ui.py
+++ b/src/bonsai/bonsai/bim/module/sequence/ui.py
@@ -88,7 +88,8 @@ class BIM_PT_work_plans(Panel):
def draw(self, context):
if not WorkPlansData.is_loaded:
WorkPlansData.load()
- self.props = context.scene.BIMWorkPlanProperties
+ assert self.layout
+ self.props = tool.Sequence.get_work_plan_props()
row = self.layout.row()
if WorkPlansData.data["total_work_plans"]:
@@ -99,7 +100,7 @@ class BIM_PT_work_plans(Panel):
for work_plan in WorkPlansData.data["work_plans"]:
self.draw_work_plan_ui(work_plan)
- def draw_work_plan_ui(self, work_plan):
+ def draw_work_plan_ui(self, work_plan: dict[str, Any]) -> None:
row = self.layout.row(align=True)
row.label(text=work_plan["name"], icon="TEXT")
if self.props.active_work_plan_id == work_plan["id"]:
@@ -121,10 +122,10 @@ class BIM_PT_work_plans(Panel):
elif self.props.editing_type == "SCHEDULES":
self.draw_work_schedule_ui()
- def draw_editable_ui(self):
+ def draw_editable_ui(self) -> None:
draw_attributes(self.props.work_plan_attributes, self.layout)
- def draw_work_schedule_ui(self):
+ def draw_work_schedule_ui(self) -> None:
if WorkPlansData.data["has_work_schedules"]:
row = self.layout.row(align=True)
row.prop(self.props, "work_schedules", text="")
@@ -177,7 +178,8 @@ class BIM_PT_work_schedules(Panel):
for work_schedule_id, work_schedule in SequenceData.data["work_schedules"].items():
self.draw_work_schedule_ui(work_schedule_id, work_schedule)
- def draw_work_schedule_ui(self, work_schedule_id, work_schedule):
+ def draw_work_schedule_ui(self, work_schedule_id: int, work_schedule: dict[str, Any]) -> None:
+ assert self.layout
if work_schedule["PredefinedType"] == "BASELINE":
self.draw_readonly_work_schedule_ui(work_schedule_id)
else:
@@ -250,7 +252,7 @@ class BIM_PT_work_schedules(Panel):
self.draw_column_ui()
self.draw_editable_task_ui(work_schedule_id)
- def draw_task_operators(self):
+ def draw_task_operators(self) -> None:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
@@ -285,9 +287,10 @@ class BIM_PT_work_schedules(Panel):
row.operator("bim.duplicate_task", text="Copy", icon="DUPLICATE").task = ifc_definition_id
row.operator("bim.remove_task", text="Delete", icon="X").task = ifc_definition_id
- def draw_column_ui(self):
+ def draw_column_ui(self) -> None:
if not self.props.should_show_column_ui:
return
+ assert self.layout
row = self.layout.row()
row.operator("bim.setup_default_task_columns", text="Setup Default Columns", icon="ANCHOR_BOTTOM")
row.alignment = "RIGHT"
@@ -318,7 +321,8 @@ class BIM_PT_work_schedules(Panel):
def draw_editable_work_schedule_ui(self):
draw_attributes(self.props.work_schedule_attributes, self.layout)
- def draw_editable_task_ui(self, work_schedule_id):
+ def draw_editable_task_ui(self, work_schedule_id: int) -> None:
+ assert self.layout
row = self.layout.row(align=True)
row.label(text="Task Tools")
row = self.layout.row(align=True)
diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py
index 2fbba0223b..5fa3315e9a 100644
--- a/src/bonsai/bonsai/bim/operator.py
+++ b/src/bonsai/bonsai/bim/operator.py
@@ -47,7 +47,7 @@ from collections.abc import Iterable
from natsort import natsorted
if TYPE_CHECKING:
- from bonsai.bim.prop import MultipleFileSelect
+ from bonsai.bim.prop import MultipleFileSelect, Attribute
class SetTab(bpy.types.Operator):
@@ -1343,7 +1343,7 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
collection_values: bpy.props.CollectionProperty(type=StrProperty, options={"SKIP_SAVE"})
@staticmethod
- def resolve_data_path(data_path: str) -> tuple[str, object]:
+ def resolve_data_path(data_path: str) -> tuple[str, "Attribute"]:
"""Resolve the data path of an object's attribute to get the attribute name and the object."""
path_parts = data_path.split(".")
obj_path = ".".join(path_parts[:-1])
@@ -1361,11 +1361,12 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
for value in string_values:
self.collection_values.add().name = value
+ assert context.window_manager
return context.window_manager.invoke_props_dialog(self)
- def get_unique_attribute_values(self):
+ def get_unique_attribute_values(self) -> list[str]:
ifc_file = tool.Ifc.get()
- unique_values = set()
+ unique_values: set[str] = set()
ifc_class = self.attribute_ifc_class
elements = ifc_file.by_type(ifc_class, include_subtypes=True)
@@ -1379,7 +1380,8 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
return list(unique_values)
- def draw(self, context):
+ def draw(self, context) -> None:
+ assert self.layout
row = self.layout.row()
row.label(text=f"Select {self.attribute_name} value:")
row = self.layout.row()
diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py
index a5148496b4..cabb1bc117 100644
--- a/src/bonsai/bonsai/tool/blender.py
+++ b/src/bonsai/bonsai/tool/blender.py
@@ -61,6 +61,7 @@ if TYPE_CHECKING:
from bonsai.bim.module.constraint.prop import BIMConstraintProperties, BIMObjectConstraintProperties
from bonsai.bim.module.csv.prop import CsvProperties
from bonsai.bim.module.diff.prop import DiffProperties
+ from bonsai.bim.module.fm.prop import BIMFMProperties
from bonsai.bim.module.group.prop import BIMGroupProperties
from bonsai.bim.module.light.prop import BIMSolarProperties, RadianceExporterProperties
@@ -1663,51 +1664,56 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_object_constraint_props(cls, obj: bpy.types.Object) -> BIMObjectConstraintProperties:
- return obj.BIMObjectConstraintProperties
+ return obj.BIMObjectConstraintProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_constraint_props(cls) -> BIMConstraintProperties:
assert (scene := bpy.context.scene)
- return scene.BIMConstraintProperties
+ return scene.BIMConstraintProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_csv_props(cls) -> CsvProperties:
assert (scene := bpy.context.scene)
- return scene.CsvProperties
+ return scene.CsvProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_diff_props(cls) -> DiffProperties:
assert (scene := bpy.context.scene)
- return scene.DiffProperties
+ return scene.DiffProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_group_props(cls) -> BIMGroupProperties:
assert (scene := bpy.context.scene)
- return scene.BIMGroupProperties
+ return scene.BIMGroupProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_bim_props(cls, scene: Optional[bpy.types.Scene] = None) -> BIMProperties:
if scene is None:
- scene = bpy.context.scene
- return scene.BIMProperties
+ assert (scene := bpy.context.scene)
+ return scene.BIMProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_object_bim_props(cls, obj: bpy.types.Object) -> BIMObjectProperties:
- return obj.BIMObjectProperties
+ return obj.BIMObjectProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_object_attribute_props(cls, obj: bpy.types.Object) -> BIMAttributeProperties:
- return obj.BIMAttributeProperties
+ return obj.BIMAttributeProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_solar_props(cls) -> BIMSolarProperties:
assert (scene := bpy.context.scene)
- return scene.BIMSolarProperties
+ return scene.BIMSolarProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_radiance_exporter_props(cls) -> RadianceExporterProperties:
assert (scene := bpy.context.scene)
- return scene.BIMRadianceExporeterProperies
+ return scene.BIMRadianceExporeterProperies # pyright: ignore[reportAttributeAccessIssue]
+
+ @classmethod
+ def get_fm_props(cls) -> BIMFMProperties:
+ assert (scene := bpy.context.scene)
+ return scene.BIMFMProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_ifc_definition_id(cls, obj: IFC_CONNECTED_TYPE) -> int:
diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py
index c0c5f260c5..bafbe74969 100644
--- a/src/bonsai/bonsai/tool/sequence.py
+++ b/src/bonsai/bonsai/tool/sequence.py
@@ -45,10 +45,11 @@ from mathutils import Color
if TYPE_CHECKING:
import bonsai.bim.prop
from bonsai.bim.module.sequence.prop import (
- BIMTaskTreeProperties,
- BIMWorkScheduleProperties,
BIMAnimationProperties,
BIMStatusProperties,
+ BIMTaskTreeProperties,
+ BIMWorkPlanProperties,
+ BIMWorkScheduleProperties,
)
@@ -58,21 +59,29 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def get_animation_props(cls) -> BIMAnimationProperties:
- return bpy.context.scene.BIMAnimationProperties
+ assert (scene := bpy.context.scene)
+ return scene.BIMAnimationProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_task_tree_props(cls) -> BIMTaskTreeProperties:
- return bpy.context.scene.BIMTaskTreeProperties
+ assert (scene := bpy.context.scene)
+ return scene.BIMTaskTreeProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_schedule_props(cls) -> BIMWorkScheduleProperties:
- return bpy.context.scene.BIMWorkScheduleProperties
+ assert (scene := bpy.context.scene)
+ return scene.BIMWorkScheduleProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_status_props(cls) -> BIMStatusProperties:
assert (scene := bpy.context.scene)
return scene.BIMStatusProperties # pyright: ignore[reportAttributeAccessIssue]
+ @classmethod
+ def get_work_plan_props(cls) -> BIMWorkPlanProperties:
+ assert (scene := bpy.context.scene)
+ return scene.BIMWorkPlanProperties # pyright: ignore[reportAttributeAccessIssue]
+
@classmethod
def get_work_plan_attributes(cls) -> dict[str, Any]:
import bonsai.bim.module.sequence.helper as helper
@@ -91,7 +100,7 @@ class Sequence(bonsai.core.tool.Sequence):
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
- props = bpy.context.scene.BIMWorkPlanProperties
+ props = cls.get_work_plan_props()
return bonsai.bim.helper.export_attributes(props.work_plan_attributes, callback)
@classmethod
@@ -101,25 +110,28 @@ class Sequence(bonsai.core.tool.Sequence):
prop.string_value = "" if prop.is_null else data[name]
return True
- props = bpy.context.scene.BIMWorkPlanProperties
+ props = cls.get_work_plan_props()
props.work_plan_attributes.clear()
bonsai.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
@classmethod
def enable_editing_work_plan(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
- bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
- bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
+ props = cls.get_work_plan_props()
+ props.active_work_plan_id = work_plan.id()
+ props.editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_work_plan(cls) -> None:
- bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
+ props = cls.get_work_plan_props()
+ props.active_work_plan_id = 0
@classmethod
def enable_editing_work_plan_schedules(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
- bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
- bpy.context.scene.BIMWorkPlanProperties.editing_type = "SCHEDULES"
+ props = cls.get_work_plan_props()
+ props.active_work_plan_id = work_plan.id()
+ props.editing_type = "SCHEDULES"
@classmethod
def get_work_schedule_attributes(cls) -> dict[str, Any]:
diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
index 6ab95a2c9f..07a42246ba 100644
--- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
+++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi
@@ -318,12 +318,21 @@ class Iterator:
def get_native(self): ...
def get_object(self, id): ...
def had_error_processing_elements(self): ...
- def initialize(self): ...
+ def initialize(self) -> bool:
+ """Return true if the iterator is initialized with any elements, false otherwise."""
+ ...
+
def next(self): ...
def process_concurrently(self): ...
def process_finished_rep(self, rep): ...
- def progress(self): ...
- def set_cache(self, cache): ...
+ def progress(self) -> int:
+ """Return current progress (0-100).
+
+ Updated after processing each representation.
+ """
+ ...
+
+ def set_cache(self, cache: GeometrySerializer): ...
def unit_magnitude(self): ...
def unit_name(self): ...