diff --git a/src/bonsai/bonsai/bim/module/boundary/decorator.py b/src/bonsai/bonsai/bim/module/boundary/decorator.py index be25c2c0d7..b9243d1a24 100644 --- a/src/bonsai/bonsai/bim/module/boundary/decorator.py +++ b/src/bonsai/bonsai/bim/module/boundary/decorator.py @@ -78,7 +78,8 @@ class BoundaryDecorator: unselected_edges = [] unselected_tris = [] - for boundary in context.scene.BIMBoundaryProperties.boundaries: + props = tool.Boundary.get_boundary_props() + for boundary in props.boundaries: obj = boundary.obj if not obj or not obj.data: # A boundary may not have data if it has no connection geometry continue diff --git a/src/bonsai/bonsai/bim/module/boundary/operator.py b/src/bonsai/bonsai/bim/module/boundary/operator.py index 6cd7f6f61a..b2d08797cc 100644 --- a/src/bonsai/bonsai/bim/module/boundary/operator.py +++ b/src/bonsai/bonsai/bim/module/boundary/operator.py @@ -354,7 +354,9 @@ class EnableEditingBoundary(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - bprops = context.active_object.BIMBoundaryProperties + obj = context.active_object + assert obj + bprops = tool.Boundary.get_object_boundary_props(obj) bprops.is_editing = True boundary = tool.Ifc.get_entity(context.active_object) for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items(): @@ -373,7 +375,9 @@ class DisableEditingBoundary(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - bprops = context.active_object.BIMBoundaryProperties + obj = context.active_object + assert obj + bprops = tool.Boundary.get_object_boundary_props(obj) bprops.is_editing = False for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items(): setattr(bprops, blender_property, None) @@ -386,8 +390,10 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - bprops = context.active_object.BIMBoundaryProperties - boundary = tool.Ifc.get_entity(context.active_object) + obj = context.active_object + assert obj + bprops = tool.Boundary.get_object_boundary_props(obj) + boundary = tool.Ifc.get_entity(obj) attributes = dict() for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items(): obj = getattr(bprops, blender_property, None) @@ -519,6 +525,7 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Boundary.get_boundary_props() to_delete = set() spaces = set() for obj in context.selected_objects: @@ -538,7 +545,7 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator): for boundary, boundary_obj in to_delete: tool.Ifc.unlink(element=boundary) bpy.data.objects.remove(boundary_obj) - context.scene.BIMBoundaryProperties.boundaries.clear() + props.boundaries.clear() return {"FINISHED"} @@ -549,7 +556,7 @@ class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMBoundaryProperties + props = tool.Boundary.get_boundary_props() # filter not decorated boundaries and add decorations for them decorated_boundaries = set([i.obj for i in props.boundaries]) active_boundaries = set() diff --git a/src/bonsai/bonsai/bim/module/boundary/prop.py b/src/bonsai/bonsai/bim/module/boundary/prop.py index 30b9fa75be..cb0067b892 100644 --- a/src/bonsai/bonsai/bim/module/boundary/prop.py +++ b/src/bonsai/bonsai/bim/module/boundary/prop.py @@ -30,23 +30,24 @@ from bpy.props import ( CollectionProperty, ) import bonsai.tool as tool +from typing import TYPE_CHECKING, Union -def space_filter(self, object): +def space_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool: entity = tool.Ifc.get_entity(object) if entity: return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement") return False -def boundary_filter(self, object): +def boundary_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool: entity = tool.Ifc.get_entity(object) if entity: return entity.is_a("IfcRelSpaceBoundary") return False -def element_filter(self, object): +def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool: entity = tool.Ifc.get_entity(object) if entity: return entity.is_a("IfcElement") @@ -60,6 +61,16 @@ class BIMObjectBoundaryProperties(PropertyGroup): parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter) corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter) + if TYPE_CHECKING: + is_editing: bool + relating_space: Union[bpy.types.Object, None] + related_building_element: Union[bpy.types.Object, None] + parent_boundary: Union[bpy.types.Object, None] + corresponding_boundary: Union[bpy.types.Object, None] + class BIMBoundaryProperties(PropertyGroup): boundaries: bpy.props.CollectionProperty(type=ObjProperty) + + if TYPE_CHECKING: + boundaries: bpy.types.bpy_prop_collection_idprop[ObjProperty] diff --git a/src/bonsai/bonsai/bim/module/boundary/ui.py b/src/bonsai/bonsai/bim/module/boundary/ui.py index ec6a15662e..e3c7d8936f 100644 --- a/src/bonsai/bonsai/bim/module/boundary/ui.py +++ b/src/bonsai/bonsai/bim/module/boundary/ui.py @@ -64,10 +64,12 @@ class BIM_PT_Boundary(Panel): return entity.is_a("IfcRelSpaceBoundary") def draw(self, context): - props = context.active_object.BIMObjectProperties + obj = context.active_object + assert obj + props = obj.BIMObjectProperties ifc_file = tool.Ifc.get() boundary = ifc_file.by_id(props.ifc_definition_id) - self.bprops = context.active_object.BIMBoundaryProperties + self.bprops = tool.Boundary.get_object_boundary_props(obj) if self.bprops.is_editing: row = self.layout.row(align=True) row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes") diff --git a/src/bonsai/bonsai/bim/module/cost/data.py b/src/bonsai/bonsai/bim/module/cost/data.py index 1630b49748..cb4dc26a71 100644 --- a/src/bonsai/bonsai/bim/module/cost/data.py +++ b/src/bonsai/bonsai/bim/module/cost/data.py @@ -325,10 +325,11 @@ class CostItemQuantitiesData: @classmethod def process_quantity_names(cls): active_task_index = bpy.context.scene.BIMWorkScheduleProperties.active_task_index - total_tasks = len(bpy.context.scene.BIMTaskTreeProperties.tasks) + tprops = tool.Sequence.get_task_tree_props() + total_tasks = len(tprops.tasks) if not total_tasks or active_task_index >= total_tasks: return [] - ifc_definition_id = bpy.context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id + ifc_definition_id = tprops.tasks[active_task_index].ifc_definition_id element = tool.Ifc.get().by_id(ifc_definition_id) names = set() qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True) diff --git a/src/bonsai/bonsai/bim/module/cost/ui.py b/src/bonsai/bonsai/bim/module/cost/ui.py index 52e93a87b7..67d14437ed 100644 --- a/src/bonsai/bonsai/bim/module/cost/ui.py +++ b/src/bonsai/bonsai/bim/module/cost/ui.py @@ -19,6 +19,7 @@ import bpy import bonsai.bim.helper import bonsai.bim.module.cost.prop as CostProp +import bonsai.tool as tool from bpy.types import Panel, UIList from bonsai.bim.ifc import IfcStore from bonsai.bim.module.cost.data import CostSchedulesData @@ -461,7 +462,7 @@ class BIM_PT_cost_item_quantities(Panel): total_cost_item_processes = len(self.props.cost_item_processes) row2.label(text="Tasks ({})".format(total_cost_item_processes)) - tprops = context.scene.BIMTaskTreeProperties + tprops = tool.Sequence.get_task_tree_props() wprops = context.scene.BIMWorkScheduleProperties if tprops.tasks and wprops.active_task_index < len(tprops.tasks): if has_quantity_names: diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 534e8c4ab1..4c35cf2139 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -422,7 +422,7 @@ class CreateDrawing(bpy.types.Operator): self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save() return svg_path - def get_linework_contexts(self, ifc, target_view) -> LineworkContexts: + def get_linework_contexts(self, ifc: ifcopenshell.file, target_view: str) -> LineworkContexts: plan_body_target_contexts = [] plan_body_model_contexts = [] model_body_target_contexts = [] diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 14d86b5b2e..faa93914dd 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -42,6 +42,7 @@ import ifcopenshell.util.representation import ifcopenshell.util.shape import ifcopenshell.util.unit import bonsai.bim.handler +import bonsai.bim.helper import bonsai.bim.schema import bonsai.tool as tool import bonsai.core.project as core diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index 4c07ca7c57..78f02bf206 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -128,7 +128,7 @@ class BIM_PT_project(Panel): self.layout.use_property_decorate = False self.layout.use_property_split = True props = context.scene.BIMProperties - pprops = context.scene.BIMProjectProperties + pprops = self.props = tool.Project.get_project_props() self.file = IfcStore.get_file() if pprops.is_loading: self.draw_advanced_loading_ui(context) @@ -148,7 +148,7 @@ class BIM_PT_project(Panel): self.draw_unsaved_project_ui(context) def draw_advanced_loading_ui(self, context): - pprops = context.scene.BIMProjectProperties + pprops = self.props prop_with_search(self.layout, pprops, "filter_mode") if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]: row = self.layout.row(align=True) @@ -222,7 +222,7 @@ class BIM_PT_project(Panel): row.operator("bim.load_project_elements") def draw_editing_buttons(self, context, row): - pprops = context.scene.BIMProjectProperties + pprops = self.props if IfcStore.get_file(): if pprops.is_editing: row.operator("bim.edit_header", icon="CHECKMARK", text="") @@ -231,7 +231,7 @@ class BIM_PT_project(Panel): row.operator("bim.enable_editing_header", icon="GREASEPENCIL", text="") def draw_editable_file_info(self, context): - pprops = context.scene.BIMProjectProperties + pprops = self.props if IfcStore.get_file(): row = self.layout.row(align=True) @@ -316,7 +316,7 @@ class BIM_PT_new_project_wizard(Panel): self.layout.use_property_split = True props = context.scene.BIMProperties - pprops = context.scene.BIMProjectProperties + pprops = tool.Project.get_project_props() prop_with_search(self.layout, pprops, "export_schema") row = self.layout.row() row.prop(context.scene.unit_settings, "system") @@ -418,7 +418,7 @@ class BIM_PT_links(Panel): bl_parent_id = "BIM_PT_tab_project_setup" def draw(self, context): - self.props = context.scene.BIMProjectProperties + self.props = tool.Project.get_project_props() row = self.layout.row(align=True) row.operator("bim.link_ifc") if self.props.links: diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py index 6506f7cce7..b71a7fabb6 100644 --- a/src/bonsai/bonsai/bim/module/pset/data.py +++ b/src/bonsai/bonsai/bim/module/pset/data.py @@ -55,7 +55,11 @@ class Data: for name, data in sorted(psetqtos.items()): pset = ifc_file.by_id(data["id"]) pset_uses = ifcopenshell.util.element.get_elements_by_pset(pset) - has_template = bool(tool.Pset.get_pset_template(name)) + pset_template = tool.Pset.get_pset_template(name) + if has_template := bool(pset_template): + template_available_in_ui = pset_template + else: + template_available_in_ui = False results.append( { "id": data["id"], @@ -214,7 +218,7 @@ class TaskQtosData(Data): @classmethod def load(cls): wprops = bpy.context.scene.BIMWorkScheduleProperties - tprops = bpy.context.scene.BIMTaskTreeProperties + tprops = tool.Sequence.get_task_tree_props() ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)} cls.is_loaded = True diff --git a/src/bonsai/bonsai/bim/module/pset/operator.py b/src/bonsai/bonsai/bim/module/pset/operator.py index 38c6517585..4165951a53 100644 --- a/src/bonsai/bonsai/bim/module/pset/operator.py +++ b/src/bonsai/bonsai/bim/module/pset/operator.py @@ -491,14 +491,14 @@ class SavePsetAsTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOpera pset_id: bpy.props.IntProperty() def invoke(self, context, event): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() if tool.Blender.get_enum_safe(props, "pset_template_files") is None: self.report({"ERROR"}, "No template files found. You can create one in Property Set Templates UI.") return {"CANCELLED"} return context.window_manager.invoke_props_dialog(self, width=250) def draw(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() self.layout.prop(props, "pset_template_files", text="Template File") def _execute(self, context): diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index fe40ab7aeb..dbec7ee476 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -40,7 +40,7 @@ from typing import Any, Optional, TYPE_CHECKING from typing_extensions import assert_never if TYPE_CHECKING: - from bonsai.bim.module.pset.prop import IfcProperty + from bonsai.bim.module.pset.prop import IfcProperty, PsetProperties def draw_property(prop: IfcProperty, layout: bpy.types.UILayout, copy_operator: Optional[str] = None) -> None: @@ -73,7 +73,7 @@ def draw_single_property(prop: IfcProperty, layout: bpy.types.UILayout, copy_ope def draw_enumerated_property( - prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None + prop: IfcProperty, layout: bpy.types.UILayout, copy_operator: Optional[str] = None ) -> None: value_name = prop.metadata.get_value_name() if not value_name: @@ -99,7 +99,7 @@ def draw_psetqto_ui( context: bpy.types.Context, pset_id: int, pset: dict[str, Any], - props: bpy.types.PropertyGroup, + props: PsetProperties, layout: bpy.types.UILayout, obj_type: tool.Ifc.OBJECT_TYPE, allow_removing: bool = True, @@ -219,9 +219,7 @@ def draw_psetqto_ui( row.label(text="No Properties") -def draw_psetqto_editable_ui( - box: bpy.types.UILayout, props: bpy.types.PropertyGroup, prop: bpy.types.PropertyGroup -) -> None: +def draw_psetqto_editable_ui(box: bpy.types.UILayout, props: PsetProperties, prop: IfcProperty) -> None: row = box.row(align=True) draw_property(prop, row, copy_operator="bim.copy_property_to_selection") @@ -493,7 +491,8 @@ class BIM_PT_task_qtos(Panel): props = context.scene.BIMWorkScheduleProperties if not props.active_work_schedule_id: return False - total_tasks = len(context.scene.BIMTaskTreeProperties.tasks) + tprops = tool.Sequence.get_task_tree_props() + total_tasks = len(tprops.tasks) if total_tasks > 0 and props.active_task_index < total_tasks: return True return False diff --git a/src/bonsai/bonsai/bim/module/pset_template/data.py b/src/bonsai/bonsai/bim/module/pset_template/data.py index 5309be7ea8..cbad09d04d 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/data.py +++ b/src/bonsai/bonsai/bim/module/pset_template/data.py @@ -126,13 +126,14 @@ class PsetTemplatesData: if not cls.data["pset_template_files"]: return [] if not IfcStore.pset_template_file: - IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files + props = tool.PsetTemplate.get_pset_template_props() + IfcStore.pset_template_path = props.pset_template_files IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path) return [(str(t.id()), t.Name, "") for t in IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")] @classmethod def pset_template(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() template_id = props.pset_templates if not template_id: return {} @@ -145,7 +146,7 @@ class PsetTemplatesData: @classmethod def prop_templates(cls) -> list[dict[str, Any]]: - props = bpy.context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() template_id = props.pset_templates if not template_id: return [] diff --git a/src/bonsai/bonsai/bim/module/pset_template/operator.py b/src/bonsai/bonsai/bim/module/pset_template/operator.py index 251ebf6011..1d0c7c619a 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/operator.py +++ b/src/bonsai/bonsai/bim/module/pset_template/operator.py @@ -49,7 +49,8 @@ class AddPsetTemplateFile(bpy.types.Operator): template.write(filepath) bonsai.bim.handler.refresh_ui_data() bonsai.bim.schema.reload(tool.Ifc.get().schema) - context.scene.BIMPsetTemplateProperties.pset_template_files = filepath + props = tool.PsetTemplate.get_pset_template_props() + props.pset_template_files = filepath tool.PsetTemplate.enable_editing_pset_template() return {"FINISHED"} @@ -67,7 +68,8 @@ class AddPsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator self.template_file.write(IfcStore.pset_template_path) bonsai.bim.handler.refresh_ui_data() bonsai.bim.schema.reload(tool.Ifc.get().schema) - context.scene.BIMPsetTemplateProperties.pset_templates = str(template.id()) + props = tool.PsetTemplate.get_pset_template_props() + props.pset_templates = str(template.id()) class RemovePsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator): @@ -76,7 +78,7 @@ class RemovePsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOpera bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() current_pset_template_id = int(props.pset_templates) if props.active_pset_template_id == current_pset_template_id: bpy.ops.bim.disable_editing_pset_template() @@ -107,7 +109,7 @@ class DisableEditingPsetTemplate(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() props.active_pset_template_id = 0 return {"FINISHED"} @@ -130,7 +132,8 @@ class DeletePropEnum(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template + props = tool.PsetTemplate.get_pset_template_props() + active_prop = props.active_prop_template active_prop.enum_values.remove(self.index) return {"FINISHED"} @@ -142,7 +145,8 @@ class AddPropEnum(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template + props = tool.PsetTemplate.get_pset_template_props() + active_prop = props.active_prop_template active_prop.enum_values.add() return {"FINISHED"} @@ -153,7 +157,7 @@ class DisableEditingPropTemplate(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() props.active_prop_template_id = 0 return {"FINISHED"} @@ -164,7 +168,7 @@ class EditPsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperato bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() ifcopenshell.api.run( "pset_template.edit_pset_template", IfcStore.pset_template_file, @@ -209,7 +213,7 @@ class RemovePsetTemplateFile(bpy.types.Operator): bonsai.bim.schema.reload(tool.Ifc.get().schema) # Ensure enum is valid after deletion. - self.props = context.scene.BIMPsetTemplateProperties + self.props = tool.PsetTemplate.get_pset_template_props() if not tool.Blender.ensure_enum_is_valid(self.props, "pset_template_files"): self.update_template_files_prop(context) return {"FINISHED"} @@ -226,7 +230,7 @@ class AddPropTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() pset_template_id = props.active_pset_template_id or int(props.pset_templates) prop_template = ifcopenshell.api.run( "pset_template.add_prop_template", @@ -264,7 +268,7 @@ class EditPropTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperato def _execute(self, context): assert IfcStore.pset_template_file - props = context.scene.BIMPsetTemplateProperties + props = tool.PsetTemplate.get_pset_template_props() active_prop_template = props.active_prop_template if props.active_prop_template.template_type == "P_ENUMERATEDVALUE": data_type = props.active_prop_template.get_value_name() diff --git a/src/bonsai/bonsai/bim/module/pset_template/ui.py b/src/bonsai/bonsai/bim/module/pset_template/ui.py index c59db76741..af46fdc0e7 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/ui.py +++ b/src/bonsai/bonsai/bim/module/pset_template/ui.py @@ -36,7 +36,7 @@ class BIM_PT_pset_template(Panel): if not PsetTemplatesData.is_loaded: PsetTemplatesData.load() - self.props = context.scene.BIMPsetTemplateProperties + self.props = tool.PsetTemplate.get_pset_template_props() row = self.layout.row(align=True) if PsetTemplatesData.data["pset_template_files"]: diff --git a/src/bonsai/bonsai/bim/module/sequence/prop.py b/src/bonsai/bonsai/bim/module/sequence/prop.py index bdb1219218..d2e550138d 100644 --- a/src/bonsai/bonsai/bim/module/sequence/prop.py +++ b/src/bonsai/bonsai/bim/module/sequence/prop.py @@ -40,6 +40,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING def getTaskColumns(self, context): @@ -343,6 +344,26 @@ class Task(PropertyGroup): is_predecessor: BoolProperty(name="Is Predecessor") is_successor: BoolProperty(name="Is Successor") + if TYPE_CHECKING: + name: str + identification: str + ifc_definition_id: int + has_children: bool + is_selected: bool + is_expanded: bool + has_bar_visual: bool + level_index: int + duration: str + start: str + finish: str + calendar: str + derived_start: str + derived_finish: str + derived_duration: str + derived_calendar: str + is_predecessor: bool + is_successor: bool + class WorkPlan(PropertyGroup): name: StringProperty(name="Name") @@ -479,6 +500,9 @@ class BIMTaskTreeProperties(PropertyGroup): # In Blender if you add many collection items it makes other property access in the same group really slow. tasks: CollectionProperty(name="Tasks", type=Task) + if TYPE_CHECKING: + tasks: bpy.types.bpy_prop_collection_idprop[Task] + class WorkCalendar(PropertyGroup): name: StringProperty(name="Name") diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index 26973acd1c..2320bb80b4 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -156,7 +156,7 @@ class BIM_PT_work_schedules(Panel): if not WorkScheduleData.is_loaded: WorkScheduleData.load() self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties + self.tprops = tool.Sequence.get_task_tree_props() if not self.props.active_work_schedule_id: row = self.layout.row(align=True) @@ -649,7 +649,8 @@ class BIM_PT_task_icom(Panel): props = context.scene.BIMWorkScheduleProperties if not props.active_work_schedule_id: return False - total_tasks = len(context.scene.BIMTaskTreeProperties.tasks) + tprops = tool.Sequence.get_task_tree_props() + total_tasks = len(tprops.tasks) if total_tasks > 0 and props.active_task_index < total_tasks: return True return False @@ -659,7 +660,7 @@ class BIM_PT_task_icom(Panel): TaskICOMData.load() self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties + self.tprops = tool.Sequence.get_task_tree_props() task = self.tprops.tasks[self.props.active_task_index] grid = self.layout.grid_flow(columns=3, even_columns=True) diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index e50de167db..caddd90975 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -169,6 +169,9 @@ class StrProperty(PropertyGroup): class ObjProperty(PropertyGroup): obj: bpy.props.PointerProperty(type=bpy.types.Object) + if TYPE_CHECKING: + obj: Union[bpy.types.Object, None] + def update_single_file(self: "MultipleFileSelect", context: bpy.types.Context) -> None: self.file_list.clear() @@ -180,6 +183,10 @@ class MultipleFileSelect(PropertyGroup): single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file) file_list: bpy.props.CollectionProperty(type=StrProperty) + if TYPE_CHECKING: + single_file: str + file_list: bpy.types.bpy_prop_collection_idprop[StrProperty] + def set_file_list(self, dirname: str, files: list[str]) -> None: self.file_list.clear() diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 7fe6551adc..d5f214a034 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -194,9 +194,8 @@ class Blender(bonsai.core.tool.Blender): elif obj_type == "MaterialSetItem": return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id elif obj_type == "Task": - return context.scene.BIMTaskTreeProperties.tasks[ - context.scene.BIMWorkScheduleProperties.active_task_index - ].ifc_definition_id + tprops = tool.Sequence.get_task_tree_props() + return tprops.tasks[context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id elif obj_type == "Cost": return context.scene.BIMCostProperties.cost_items[ context.scene.BIMCostProperties.active_cost_item_index diff --git a/src/bonsai/bonsai/tool/boundary.py b/src/bonsai/bonsai/tool/boundary.py index f00dec4228..dfa8962fff 100644 --- a/src/bonsai/bonsai/tool/boundary.py +++ b/src/bonsai/bonsai/tool/boundary.py @@ -16,15 +16,27 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import mathutils import bonsai.core.tool import bonsai.tool as tool from mathutils import Matrix, Vector -from typing import Any, Sequence +from typing import Any, Sequence, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.boundary.prop import BIMBoundaryProperties, BIMObjectBoundaryProperties class Boundary(bonsai.core.tool.Boundary): + @classmethod + def get_boundary_props(cls) -> BIMBoundaryProperties: + return bpy.context.scene.BIMBoundaryProperties + + @classmethod + def get_object_boundary_props(cls, obj: bpy.types.Object) -> BIMObjectBoundaryProperties: + return obj.BIMBoundaryProperties + @classmethod def get_assign_connection_geometry_settings(cls, obj: bpy.types.Object) -> dict[str, Any]: from bonsai.bim.module.geometry.helper import Helper @@ -76,7 +88,8 @@ class Boundary(bonsai.core.tool.Boundary): @classmethod def decorate_boundary(cls, obj: bpy.types.Object) -> None: - new = bpy.context.scene.BIMBoundaryProperties.boundaries.add() + props = cls.get_boundary_props() + new = props.boundaries.add() new.obj = obj obj.show_in_front = True diff --git a/src/bonsai/bonsai/tool/pset.py b/src/bonsai/bonsai/tool/pset.py index 05dde3c7b3..1bb242501b 100644 --- a/src/bonsai/bonsai/tool/pset.py +++ b/src/bonsai/bonsai/tool/pset.py @@ -135,7 +135,7 @@ class Pset(bonsai.core.tool.Pset): return special_type @classmethod - def import_pset_from_existing(cls, pset: ifcopenshell.entity_instance, props: bpy.types.PropertyGroup) -> None: + def import_pset_from_existing(cls, pset: ifcopenshell.entity_instance, props: PsetProperties) -> None: pset_props = [] if pset.is_a("IfcElementQuantity"): pset_props = pset.Quantities diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 343a0338f2..4dd4cfbb99 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -42,12 +42,17 @@ from typing import Optional, Any, Union, Literal, TYPE_CHECKING, Iterable if TYPE_CHECKING: import bonsai.bim.prop + from bonsai.bim.module.sequence.prop import BIMTaskTreeProperties class Sequence(bonsai.core.tool.Sequence): RELATED_OBJECT_TYPE = Literal["RESOURCE", "PRODUCT", "CONTROL"] + @classmethod + def get_task_tree_props(cls) -> BIMTaskTreeProperties: + return bpy.context.scene.BIMTaskTreeProperties + @classmethod def get_work_plan_attributes(cls) -> dict[str, Any]: import bonsai.bim.module.sequence.helper as helper @@ -146,7 +151,8 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_task_tree(cls, work_schedule: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMTaskTreeProperties.tasks.clear() + props = cls.get_task_tree_props() + props.tasks.clear() props = bpy.context.scene.BIMWorkScheduleProperties cls.contracted_tasks = json.loads(props.contracted_tasks) @@ -185,7 +191,8 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def create_new_task_li(cls, related_object_id: int, level_index: int) -> None: task = tool.Ifc.get().by_id(related_object_id) - new = bpy.context.scene.BIMTaskTreeProperties.tasks.add() + props = cls.get_task_tree_props() + new = props.tasks.add() new.ifc_definition_id = related_object_id new.is_expanded = related_object_id not in cls.contracted_tasks new.level_index = level_index @@ -199,7 +206,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_task_properties(cls, task: Optional[ifcopenshell.entity_instance] = None) -> None: props = bpy.context.scene.BIMWorkScheduleProperties - task_props = bpy.context.scene.BIMTaskTreeProperties + task_props = cls.get_task_tree_props() tasks_with_visual_bar = cls.get_task_bar_list() props.is_task_update_enabled = False @@ -281,8 +288,9 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def contract_all_tasks(cls) -> None: props = bpy.context.scene.BIMWorkScheduleProperties + tprops = cls.get_task_tree_props() contracted_tasks = json.loads(props.contracted_tasks) - for task_item in bpy.context.scene.BIMTaskTreeProperties.tasks: + for task_item in tprops.tasks: if task_item.is_expanded: contracted_tasks.append(task_item.ifc_definition_id) props.contracted_tasks = json.dumps(contracted_tasks) @@ -302,7 +310,7 @@ class Sequence(bonsai.core.tool.Sequence): def disable_selecting_deleted_task(cls) -> None: props = bpy.context.scene.BIMWorkScheduleProperties if props.active_task_id not in [ - task.ifc_definition_id for task in bpy.context.scene.BIMTaskTreeProperties.tasks + task.ifc_definition_id for task in cls.get_task_tree_props().tasks ]: # Task was deleted bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 @@ -310,9 +318,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_checked_tasks(cls) -> list[ifcopenshell.entity_instance]: return [ - tool.Ifc.get().by_id(task.ifc_definition_id) - for task in bpy.context.scene.BIMTaskTreeProperties.tasks - if task.is_selected + tool.Ifc.get().by_id(task.ifc_definition_id) for task in cls.get_task_tree_props().tasks if task.is_selected ] or [] @classmethod @@ -486,7 +492,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_highlighted_task(cls) -> Union[ifcopenshell.entity_instance, None]: - tasks = bpy.context.scene.BIMTaskTreeProperties.tasks + tasks = cls.get_task_tree_props().tasks if len(tasks) and len(tasks) > bpy.context.scene.BIMWorkScheduleProperties.active_task_index: return tool.Ifc.get().by_id( tasks[bpy.context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id @@ -789,7 +795,7 @@ class Sequence(bonsai.core.tool.Sequence): cls.load_task_tree(work_schedule) cls.load_task_properties() - task_props = bpy.context.scene.BIMTaskTreeProperties + task_props = cls.get_task_tree_props() expanded_tasks = [item.ifc_definition_id for item in task_props.tasks] bpy.context.scene.BIMWorkScheduleProperties.active_task_index = expanded_tasks.index(task.id()) or 0 diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index de2b271c55..ec16526ca4 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -1133,23 +1133,27 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def toggle_spaces_visibility_wired_and_textured(cls, spaces: list[ifcopenshell.entity_instance]) -> None: first_obj = tool.Ifc.get_object(spaces[0]) - if bpy.data.objects[first_obj.name].display_type == "TEXTURED": + assert isinstance(first_obj, bpy.types.Object) + obj: bpy.types.Object + if first_obj.display_type == "TEXTURED": for space in spaces: obj = tool.Ifc.get_object(space) - bpy.data.objects[obj.name].show_wire = True - bpy.data.objects[obj.name].display_type = "WIRE" + obj.show_wire = True + obj.display_type = "WIRE" return - elif bpy.data.objects[first_obj.name].display_type == "WIRE": + elif first_obj.display_type == "WIRE": for space in spaces: obj = tool.Ifc.get_object(space) - bpy.data.objects[obj.name].show_wire = False - bpy.data.objects[obj.name].display_type = "TEXTURED" + obj.show_wire = False + obj.display_type = "TEXTURED" return @classmethod def toggle_hide_spaces(cls, spaces: list[ifcopenshell.entity_instance]) -> None: first_obj = tool.Ifc.get_object(spaces[0]) + assert isinstance(first_obj, bpy.types.Object) + obj: bpy.types.Object if first_obj.hide_get() == False: for space in spaces: obj = tool.Ifc.get_object(space) diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 682bbefce4..eade8ad366 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -352,7 +352,8 @@ def i_select_the_item_name_item_in_the_list_name_list(item_name, list_name): @when("I load a new pset template file") def i_load_a_new_pset_template_file(): - IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files + props = tool.PsetTemplate.get_pset_template_props() + IfcStore.pset_template_path = props.pset_template_files IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/assign_port.py b/src/ifcopenshell-python/ifcopenshell/api/system/assign_port.py index bfa3b5e37f..8506e6b53d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/assign_port.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/assign_port.py @@ -21,6 +21,7 @@ import ifcopenshell.api.owner import ifcopenshell.api.geometry import ifcopenshell.guid import ifcopenshell.util.placement +from typing import Any def assign_port( @@ -68,6 +69,9 @@ def assign_port( class Usecase: + file: ifcopenshell.file + settings: dict[str, Any] + def execute(self): if self.file.schema == "IFC2X3": return self.execute_ifc2x3()