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): ...