diff --git a/src/bonsai/bonsai/bim/module/cost/data.py b/src/bonsai/bonsai/bim/module/cost/data.py index feee0a4c64..4e4e82f6c1 100644 --- a/src/bonsai/bonsai/bim/module/cost/data.py +++ b/src/bonsai/bonsai/bim/module/cost/data.py @@ -369,13 +369,13 @@ class CostItemQuantitiesData: cls.is_loaded = True @classmethod - def product_quantity_names(cls): - elements = tool.Spatial.get_selected_products() + def product_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: + elements = list(tool.Spatial.get_selected_products()) names = ifcopenshell.util.cost.get_product_quantity_names(elements) return [(n, n, "") for n in names] @classmethod - def process_quantity_names(cls): + def process_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: props = tool.Sequence.get_work_schedule_props() active_task_index = props.active_task_index tprops = tool.Sequence.get_task_tree_props() @@ -391,14 +391,11 @@ class CostItemQuantitiesData: return [(n, n, "") for n in names if n != "id"] @classmethod - def resource_quantity_names(cls): - active_resource_index = bpy.context.scene.BIMResourceProperties.active_resource_index - total_resources = len(bpy.context.scene.BIMResourceTreeProperties.resources) - if not total_resources or active_resource_index >= total_resources: + def resource_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: + active_resource = tool.Resource.get_resource_props().active_resource + if not active_resource: return [] - ifc_definition_id = bpy.context.scene.BIMResourceTreeProperties.resources[ - active_resource_index - ].ifc_definition_id + ifc_definition_id = active_resource.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 b96cc6cb30..738e793fba 100644 --- a/src/bonsai/bonsai/bim/module/cost/ui.py +++ b/src/bonsai/bonsai/bim/module/cost/ui.py @@ -393,7 +393,7 @@ class BIM_PT_cost_item_types(Panel): op.cost_item = cost_item.ifc_definition_id rtprops = context.scene.BIMResourceTreeProperties - rprops = context.scene.BIMResourceProperties + rprops = tool.Resource.get_resource_props() if rtprops.resources and rprops.active_resource_index < len(rtprops.resources): if has_quantity_names: op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") @@ -552,9 +552,8 @@ class BIM_PT_cost_item_quantities(Panel): op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC") op.cost_item = cost_item.ifc_definition_id - rtprops = context.scene.BIMResourceTreeProperties - rprops = context.scene.BIMResourceProperties - if rtprops.resources and rprops.active_resource_index < len(rtprops.resources): + active_resource = tool.Resource.get_resource_props().active_resource + if active_resource: if has_quantity_names: op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") op.related_object_type = "RESOURCE" diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py index 802a7f8319..272d8e8709 100644 --- a/src/bonsai/bonsai/bim/module/pset/data.py +++ b/src/bonsai/bonsai/bim/module/pset/data.py @@ -239,9 +239,9 @@ class ResourceQtosData(Data): @classmethod def load(cls): - rprops = bpy.context.scene.BIMResourceProperties - rtprops = bpy.context.scene.BIMResourceTreeProperties - ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id + active_resource = tool.Resource.get_resource_props().active_resource + assert active_resource + ifc_definition_id = active_resource.ifc_definition_id cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)} cls.is_loaded = True @@ -252,9 +252,9 @@ class ResourcePsetsData(Data): @classmethod def load(cls): - rprops = bpy.context.scene.BIMResourceProperties - rtprops = bpy.context.scene.BIMResourceTreeProperties - ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id + active_resource = tool.Resource.get_resource_props().active_resource + assert active_resource + ifc_definition_id = active_resource.ifc_definition_id cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)} cls.is_loaded = True diff --git a/src/bonsai/bonsai/bim/module/pset/prop.py b/src/bonsai/bonsai/bim/module/pset/prop.py index d5cf85791d..697ca06b16 100644 --- a/src/bonsai/bonsai/bim/module/pset/prop.py +++ b/src/bonsai/bonsai/bim/module/pset/prop.py @@ -155,9 +155,9 @@ def get_task_qto_names(self: "PsetProperties", context: object) -> tool.Blender. def get_resource_pset_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: global psetnames - rprops = context.scene.BIMResourceProperties - rtprops = context.scene.BIMResourceTreeProperties - ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() + active_resource = tool.Resource.get_resource_props().active_resource + assert active_resource + ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a() if ifc_class not in psetnames: psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema()) psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) @@ -166,9 +166,9 @@ def get_resource_pset_names(self: "PsetProperties", context: bpy.types.Context) def get_resource_qto_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: global qtonames - rprops = context.scene.BIMResourceProperties - rtprops = context.scene.BIMResourceTreeProperties - ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() + active_resource = tool.Resource.get_resource_props().active_resource + assert active_resource + ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a() if ifc_class not in qtonames: psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema()) qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index bb571a842e..7e25db62ca 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -596,11 +596,8 @@ class BIM_PT_resource_qtos(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMResourceProperties - total_resources = len(context.scene.BIMResourceTreeProperties.resources) - if total_resources > 0 and props.active_resource_index < total_resources: - return True - return False + active_resource = tool.Resource.get_resource_props().active_resource + return bool(active_resource) def draw(self, context): if not ResourceQtosData.is_loaded: @@ -631,11 +628,8 @@ class BIM_PT_resource_psets(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMResourceProperties - total_resources = len(context.scene.BIMResourceTreeProperties.resources) - if total_resources > 0 and props.active_resource_index < total_resources: - return True - return False + active_resource = tool.Resource.get_resource_props().active_resource + return bool(active_resource) def draw(self, context): if not ResourcePsetsData.is_loaded: diff --git a/src/bonsai/bonsai/bim/module/resource/data.py b/src/bonsai/bonsai/bim/module/resource/data.py index 4c7f5f57f4..601b61eb7b 100644 --- a/src/bonsai/bonsai/bim/module/resource/data.py +++ b/src/bonsai/bonsai/bim/module/resource/data.py @@ -124,7 +124,7 @@ class ResourceData: @classmethod def cost_values(cls) -> list[dict[str, Any]]: results = [] - ifc_id = bpy.context.scene.BIMResourceProperties.active_resource_id + ifc_id = tool.Resource.get_resource_props().active_resource_id if not ifc_id: return results resource = tool.Ifc.get().by_id(ifc_id) diff --git a/src/bonsai/bonsai/bim/module/resource/operator.py b/src/bonsai/bonsai/bim/module/resource/operator.py index 9e6afaa393..4d9a28c061 100644 --- a/src/bonsai/bonsai/bim/module/resource/operator.py +++ b/src/bonsai/bonsai/bim/module/resource/operator.py @@ -87,10 +87,11 @@ class EditResource(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Resource.get_resource_props() core.edit_resource( tool.Ifc, tool.Resource, - resource=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_id), + resource=tool.Ifc.get().by_id(props.active_resource_id), ) @@ -174,10 +175,11 @@ class EditResourceTime(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Resource.get_resource_props() core.edit_resource_time( tool.Ifc, tool.Resource, - resource_time=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_time_id), + resource_time=tool.Ifc.get().by_id(props.active_resource_time_id), ) diff --git a/src/bonsai/bonsai/bim/module/resource/prop.py b/src/bonsai/bonsai/bim/module/resource/prop.py index 710a3ff31d..8b258cc752 100644 --- a/src/bonsai/bonsai/bim/module/resource/prop.py +++ b/src/bonsai/bonsai/bim/module/resource/prop.py @@ -24,7 +24,7 @@ import bonsai.tool as tool import bonsai.bim.module.pset.data import bonsai.bim.module.resource.data import bonsai.bim.module.sequence.data -from bonsai.bim.prop import StrProperty, Attribute +from bonsai.bim.prop import Attribute from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -36,11 +36,12 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING, Literal, get_args -quantitytypes_enum = {} +quantitytypes_enum: dict[str, tool.Blender.BLENDER_ENUM_ITEMS] = {} -def setup_quantity_types_enum(): +def setup_quantity_types_enum() -> None: resources = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES for resource, quantities in resources.items(): quantitytypes_enum[resource] = [(q, q, "") for q in quantities] @@ -49,8 +50,8 @@ def setup_quantity_types_enum(): setup_quantity_types_enum() -def updateResourceName(self, context): - props = context.scene.BIMResourceProperties +def updateResourceName(self: "Resource", context: object) -> None: + props = tool.Resource.get_resource_props() if not props.is_resource_update_enabled: return ifc_file = tool.Ifc.get() @@ -66,18 +67,19 @@ def updateResourceName(self, context): tool.Sequence.refresh_task_resources() -def get_quantity_types(self, context): +def get_quantity_types(self: "BIMResourceProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS: return quantitytypes_enum[self.active_resource_class] -def update_active_resource_index(self, context): +def update_active_resource_index(self: "BIMResourceProperties", context: object) -> None: bonsai.bim.module.pset.data.refresh() if self.should_show_resource_tools: tool.Resource.load_productivity_data() -def updateResourceUsage(self, context): - if not context.scene.BIMResourceProperties.is_resource_update_enabled: +def updateResourceUsage(self: "Resource", context: object) -> None: + props = tool.Resource.get_resource_props() + if not props.is_resource_update_enabled: return if not self.schedule_usage: return @@ -102,6 +104,15 @@ class ISODuration(PropertyGroup): minutes: IntProperty(name="Minutes", default=0) seconds: IntProperty(name="Seconds", default=0) + if TYPE_CHECKING: + name: str + years: int + months: int + days: int + hours: int + minutes: int + seconds: int + class Resource(PropertyGroup): name: StringProperty(name="Name", update=updateResourceName) @@ -111,10 +122,26 @@ class Resource(PropertyGroup): is_expanded: BoolProperty(name="Is Expanded") level_index: IntProperty(name="Level Index") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + schedule_usage: float + has_children: bool + is_expanded: bool + level_index: int + +# Separate from BIMResourceProperties - see BIMTaskTreeProperties note. class BIMResourceTreeProperties(PropertyGroup): resources: CollectionProperty(name="Resources", type=Resource) + if TYPE_CHECKING: + resources: bpy.types.bpy_prop_collection_idprop[Resource] + + +EditingResourceType = Literal["ATTRIBUTES", "USAGE", "COSTS", "QUANTITY"] +CostType = Literal["FIXED", "SUM", "CATEGORY"] + class BIMResourceProperties(PropertyGroup): resource_attributes: CollectionProperty(name="Resource Attributes", type=Attribute) @@ -128,13 +155,7 @@ class BIMResourceProperties(PropertyGroup): active_resource_time_id: IntProperty(name="Active Resource Usage Id") resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute) editing_resource_type: EnumProperty( - name="Editing Resource Type", - items=( - ("ATTRIBUTES", "", ""), - ("USAGE", "", ""), - ("COSTS", "", ""), - ("QUANTITY", "", ""), - ), + name="Editing Resource Type", items=[(i, i, "") for i in get_args(EditingResourceType)] ) cost_types: EnumProperty( items=[ @@ -154,9 +175,56 @@ class BIMResourceProperties(PropertyGroup): quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) should_show_resource_tools: BoolProperty(name="Edit Productivity", update=update_active_resource_index) + if TYPE_CHECKING: + resource_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + is_editing: bool + active_resource_index: int + active_resource_id: int + active_resource_class: str + contracted_resources: str + is_resource_update_enabled: bool + is_loaded: bool + active_resource_time_id: int + resource_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + editing_resource_type: EditingResourceType + cost_types: CostType + cost_category: str + active_cost_value_id: int + cost_value_editing_type: str + cost_value_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + cost_value_formula: str + quantity_types: str + is_editing_quantity: bool + quantity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + should_show_resource_tools: bool + + @property + def tree(self) -> "BIMResourceTreeProperties": + assert bpy.context.scene + tprops = bpy.context.scene.BIMResourceTreeProperties # pyright: ignore[reportAttributeAccessIssue] + assert isinstance(tprops, BIMResourceTreeProperties) + return tprops + + @property + def productivity(self) -> "BIMResourceProductivity": + assert bpy.context.scene + productivity = bpy.context.scene.BIMResourceProductivity + assert isinstance(productivity, BIMResourceProductivity) + return productivity + + @property + def active_resource(self) -> Resource | None: + return tool.Blender.get_active_uilist_element(self.tree.resources, self.active_resource_index) + class BIMResourceProductivity(PropertyGroup): ifc_definition_id: IntProperty(name="IFC Definition ID") quantity_consumed: CollectionProperty(name="Duration", type=ISODuration) quantity_produced: FloatProperty(name="Quantity Produced") quantity_produced_name: StringProperty(name="Quantity Produced Name") + + if TYPE_CHECKING: + ifc_definition_id: int + quantity_consumed: bpy.types.bpy_prop_collection_idprop[ISODuration] + quantity_produced: float + quantity_produced_name: str diff --git a/src/bonsai/bonsai/bim/module/resource/ui.py b/src/bonsai/bonsai/bim/module/resource/ui.py index 6e988a58e3..4476c01293 100644 --- a/src/bonsai/bonsai/bim/module/resource/ui.py +++ b/src/bonsai/bonsai/bim/module/resource/ui.py @@ -16,12 +16,16 @@ # 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 bonsai.tool as tool import bonsai.bim.helper from bpy.types import Panel, UIList from bonsai.bim.module.resource.data import ResourceData -from typing import Any +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.resource.prop import ISODuration, Resource, BIMResourceTreeProperties class BIM_PT_resources(Panel): @@ -38,9 +42,10 @@ class BIM_PT_resources(Panel): file = tool.Ifc.get() return file and hasattr(file, "schema") and file.schema != "IFC2X3" + layout: bpy.types.UILayout + def draw(self, context): - self.props = context.scene.BIMResourceProperties - self.tprops = context.scene.BIMResourceTreeProperties + self.props = tool.Resource.get_resource_props() if not ResourceData.is_loaded: ResourceData.load() @@ -64,7 +69,7 @@ class BIM_PT_resources(Panel): self.layout.template_list( "BIM_UL_resources", "", - self.tprops, + self.props.tree, "resources", self.props, "active_resource_index", @@ -79,18 +84,18 @@ class BIM_PT_resources(Panel): self.draw_editable_resource_costs_ui() elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE": self.draw_editable_resource_time_attributes_ui() - self.draw_productivity_ui(context) + self.draw_productivity_ui() - def draw_productivity_ui(self, context): + def draw_productivity_ui(self) -> None: row = self.layout.row(align=True) row.alignment = "RIGHT" row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST") if self.props.should_show_resource_tools: - total_resources = len(self.tprops.resources) - if not total_resources or self.props.active_resource_index >= total_resources: + active_resource = self.props.active_resource + if not active_resource: return - ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id + ifc_definition_id = active_resource.ifc_definition_id resource = ResourceData.data["resources"][ifc_definition_id] if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]: @@ -145,7 +150,7 @@ class BIM_PT_resources(Panel): row2_col1 = col1.row() row2_col1.label(text="Schedule Usage") row2col2 = col2.row() - row2col2.prop(self.tprops.resources[self.props.active_resource_index], "schedule_usage", text="") + row2col2.prop(active_resource, "schedule_usage", text="") row2col3 = col3.row() row2col3.operator("bim.calculate_resource_usage", text="", icon="TEMP") op = row2col3.operator( @@ -189,7 +194,7 @@ class BIM_PT_resources(Panel): row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA") row.operator("bim.add_productivity_data", text="", icon="ADD") - def draw_resource_operators(self): + def draw_resource_operators(self) -> None: row = self.layout.row(align=True) op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT") op.ifc_class = "IfcSubContractResource" @@ -198,11 +203,11 @@ class BIM_PT_resources(Panel): op.ifc_class = "IfcCrewResource" op.parent_resource = 0 - total_resources = len(self.tprops.resources) - if not total_resources or self.props.active_resource_index >= total_resources: + active_resource = self.props.active_resource + if not active_resource: return - ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id + ifc_definition_id = active_resource.ifc_definition_id resource = ResourceData.data["resources"][ifc_definition_id] if resource["type"] != "IfcSubContractResource": @@ -239,13 +244,13 @@ class BIM_PT_resources(Panel): row.operator("bim.edit_resource_time", text="", icon="CHECKMARK") row.operator("bim.disable_editing_resource", text="", icon="CANCEL") - def draw_editable_resource_attributes_ui(self): + def draw_editable_resource_attributes_ui(self) -> None: bonsai.bim.helper.draw_attributes(self.props.resource_attributes, self.layout) - def draw_editable_resource_time_attributes_ui(self): + def draw_editable_resource_time_attributes_ui(self) -> None: bonsai.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout) - def draw_editable_resource_quantity_ui(self): + def draw_editable_resource_quantity_ui(self) -> None: resource = ResourceData.data["resources"][self.props.active_resource_id] if resource["BaseQuantity"]: @@ -278,7 +283,7 @@ class BIM_PT_resources(Panel): op.resource = self.props.active_resource_id op.ifc_class = self.props.quantity_types - def draw_editable_resource_costs_ui(self): + def draw_editable_resource_costs_ui(self) -> None: row = self.layout.row(align=True) row.prop(self.props, "cost_types", text="") if self.props.cost_types == "CATEGORY": @@ -326,16 +331,6 @@ class BIM_PT_resources(Panel): op.parent = parent_id op.cost_value = cost_value_id - def draw_duration_property(self, duration_props, layout): - for duration_prop in duration_props: - if duration_prop.name == "BaseQuantityConsumed": - layout.prop(duration_prop, "years", text="Y") - layout.prop(duration_prop, "months", text="M") - layout.prop(duration_prop, "days", text="D") - layout.prop(duration_prop, "hours", text="H") - layout.prop(duration_prop, "minutes", text="Min") - layout.prop(duration_prop, "seconds", text="S") - class BIM_UL_resources(UIList): @classmethod @@ -358,7 +353,16 @@ class BIM_UL_resources(UIList): col = row.column() col.label(text="") - 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: BIMResourceTreeProperties, + item: Resource, + icon, + active_data, + active_propname, + ) -> None: icon_map = { "IfcSubContractResource": "TEXT", "IfcCrewResource": "COMMUNITY", @@ -369,7 +373,7 @@ class BIM_UL_resources(UIList): } if item: resource = ResourceData.data["resources"][item.ifc_definition_id] - props = context.scene.BIMResourceProperties + props = tool.Resource.get_resource_props() row = layout.row(align=True) for i in range(0, item.level_index): row.label(text="", icon="BLANK1") @@ -403,8 +407,12 @@ class BIM_UL_resources(UIList): row.operator("bim.disable_editing_resource", text="", icon="CANCEL") -def draw_productivity_ui(self, context): - def draw_duration_property(duration_props, layout): +def draw_productivity_ui(self: bpy.types.Operator, context: object) -> None: + assert self.layout + + def draw_duration_property( + duration_props: bpy.types.bpy_prop_collection_idprop[ISODuration], layout: bpy.types.UILayout + ) -> None: for duration_prop in duration_props: if duration_prop.name == "BaseQuantityConsumed": layout.prop(duration_prop, "years", text="Y") @@ -414,7 +422,7 @@ def draw_productivity_ui(self, context): layout.prop(duration_prop, "minutes", text="Min") layout.prop(duration_prop, "seconds", text="S") - productivity_props = context.scene.BIMResourceProductivity + productivity_props = tool.Resource.get_resource_props().productivity grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False) col1 = grid.column(align=False) col2 = grid.column(align=False) diff --git a/src/bonsai/bonsai/bim/module/sequence/data.py b/src/bonsai/bonsai/bim/module/sequence/data.py index 82caef39fe..4caf90b9f1 100644 --- a/src/bonsai/bonsai/bim/module/sequence/data.py +++ b/src/bonsai/bonsai/bim/module/sequence/data.py @@ -386,12 +386,11 @@ class TaskICOMData: cls.is_loaded = True @classmethod - def can_active_resource_be_assigned(cls): - resource_props = bpy.context.scene.BIMResourceProperties - resource_tprops = bpy.context.scene.BIMResourceTreeProperties - total_resources = len(resource_tprops.resources) - if total_resources and resource_props.active_resource_index < total_resources: - resource_id = resource_tprops.resources[resource_props.active_resource_index].ifc_definition_id + def can_active_resource_be_assigned(cls) -> bool: + props = tool.Resource.get_resource_props() + active_resource = props.active_resource + if active_resource: + resource_id = active_resource.ifc_definition_id return not tool.Ifc.get().by_id(resource_id).is_a("IfcCrewResource") return False diff --git a/src/bonsai/bonsai/bim/module/sequence/prop.py b/src/bonsai/bonsai/bim/module/sequence/prop.py index fd2c9ec71d..26d683a852 100644 --- a/src/bonsai/bonsai/bim/module/sequence/prop.py +++ b/src/bonsai/bonsai/bim/module/sequence/prop.py @@ -308,8 +308,9 @@ def updateAssignedResourceName(self, context): pass -def updateAssignedResourceUsage(self, context): - if not context.scene.BIMResourceProperties.is_resource_update_enabled: +def updateAssignedResourceUsage(self: "TaskResource", context: object) -> None: + props = tool.Resource.get_resource_props() + if not props.is_resource_update_enabled: return if not self.schedule_usage: return diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 8404b3c4c9..864a994b91 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -246,9 +246,9 @@ class Blender(bonsai.core.tool.Blender): cost_props = tool.Cost.get_cost_props() return cost_props.cost_items[cost_props.active_cost_item_index].ifc_definition_id elif obj_type == "Resource": - return context.scene.BIMResourceTreeProperties.resources[ - context.scene.BIMResourceProperties.active_resource_index - ].ifc_definition_id + active_resource = tool.Resource.get_resource_props().active_resource + assert active_resource + return active_resource.ifc_definition_id elif obj_type == "Profile": props = tool.Profile.get_profile_props() return props.profiles[props.active_profile_index].ifc_definition_id diff --git a/src/bonsai/bonsai/tool/resource.py b/src/bonsai/bonsai/tool/resource.py index 65448440f6..425dce1a76 100644 --- a/src/bonsai/bonsai/tool/resource.py +++ b/src/bonsai/bonsai/tool/resource.py @@ -37,18 +37,25 @@ from typing import Any, Union, TYPE_CHECKING, Literal if TYPE_CHECKING: from bonsai.bim.prop import Attribute - from bonsai.bim.module.resource.prop import BIMResourceProperties + from bonsai.bim.module.resource.prop import BIMResourceProperties, BIMResourceProductivity class Resource(bonsai.core.tool.Resource): @classmethod def get_resource_props(cls) -> BIMResourceProperties: + """ + BIMResourceTreeProperties can be accessed using `.tree`. + BIMResourceProductivity - using `.productivity`. + """ return bpy.context.scene.BIMResourceProperties @classmethod def load_resources(cls) -> None: - def create_new_resource_li(resource, level_index): - new = bpy.context.scene.BIMResourceTreeProperties.resources.add() + props = cls.get_resource_props() + tprops = props.tree + + def create_new_resource_li(resource: ifcopenshell.entity_instance, level_index: int) -> None: + new = tprops.resources.add() new.ifc_definition_id = resource.id() new.is_expanded = resource.id() not in contracted_resources new.level_index = level_index @@ -61,8 +68,6 @@ class Resource(bonsai.core.tool.Resource): for nested_resource in rel.RelatedObjects ] - props = bpy.context.scene.BIMResourceProperties - tprops = bpy.context.scene.BIMResourceTreeProperties tprops.resources.clear() contracted_resources = json.loads(props.contracted_resources) props.is_resource_update_enabled = False @@ -77,8 +82,8 @@ class Resource(bonsai.core.tool.Resource): @classmethod def load_resource_properties(cls) -> None: - props = bpy.context.scene.BIMResourceProperties - tprops = bpy.context.scene.BIMResourceTreeProperties + props = cls.get_resource_props() + tprops = props.tree props.is_resource_update_enabled = False for item in tprops.resources: resource = tool.Ifc.get().by_id(item.ifc_definition_id) @@ -90,20 +95,23 @@ class Resource(bonsai.core.tool.Resource): @classmethod def disable_editing_resource(cls) -> None: - bpy.context.scene.BIMResourceProperties.active_resource_id = 0 - bpy.context.scene.BIMResourceProperties.active_resource_time_id = 0 + props = cls.get_resource_props() + props.property_unset("active_resource_id") + props.property_unset("active_resource_time_id") @classmethod def disable_resource_editing_ui(cls) -> None: - bpy.context.scene.BIMResourceProperties.is_editing = False + props = cls.get_resource_props() + props.property_unset("is_editing") @classmethod def load_resource_attributes(cls, resource: ifcopenshell.entity_instance) -> None: - bonsai.bim.helper.import_attributes(resource, bpy.context.scene.BIMResourceProperties.resource_attributes) + props = cls.get_resource_props() + bonsai.bim.helper.import_attributes(resource, props.resource_attributes) @classmethod def enable_editing_resource(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.active_resource_id = resource.id() props.resource_attributes.clear() props.editing_resource_type = "ATTRIBUTES" @@ -111,11 +119,12 @@ class Resource(bonsai.core.tool.Resource): @classmethod def get_resource_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.resource_attributes) + props = cls.get_resource_props() + return bonsai.bim.helper.export_attributes(props.resource_attributes) @classmethod def enable_editing_resource_time(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.resource_time_attributes.clear() props.active_resource_time_id = resource.Usage.id() props.active_resource_id = resource.id() @@ -128,8 +137,10 @@ class Resource(bonsai.core.tool.Resource): @classmethod def load_resource_time_attributes(cls, resource_time: ifcopenshell.entity_instance) -> None: - def callback(name, prop, data): - if prop.data_type == "string": + props = cls.get_resource_props() + + def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]) -> None | Literal[True]: + if prop and prop.data_type == "string": if isinstance(data[name], datetime): prop.string_value = "" if prop.is_null else data[name].isoformat() return True @@ -137,9 +148,7 @@ class Resource(bonsai.core.tool.Resource): prop.string_value = "" if prop.is_null else ifcdateutils.datetime2ifc(data[name], "IfcDuration") return True - bonsai.bim.helper.import_attributes( - resource_time, bpy.context.scene.BIMResourceProperties.resource_time_attributes, callback - ) + bonsai.bim.helper.import_attributes(resource_time, props.resource_time_attributes, callback) @classmethod def get_resource_time_attributes(cls) -> dict[str, Any]: @@ -160,25 +169,25 @@ class Resource(bonsai.core.tool.Resource): return True return False - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() return bonsai.bim.helper.export_attributes(props.resource_time_attributes, callback) @classmethod def enable_editing_resource_costs(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.active_resource_id = resource.id() props.editing_resource_type = "COSTS" cls.update_cost_values_ui_data() @classmethod def disable_editing_resource_cost_value(cls) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.active_cost_value_id = 0 props.cost_value_editing_type = "" @classmethod def enable_editing_resource_cost_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.cost_value_attributes.clear() props.active_cost_value_id = cost_value.id() props.cost_value_editing_type = "FORMULA" @@ -234,48 +243,44 @@ class Resource(bonsai.core.tool.Resource): prop.enum_value = str(data["UnitBasis"].UnitComponent.id()) return True - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() bonsai.bim.helper.import_attributes(cost_value, props.cost_value_attributes, callback) @classmethod def enable_editing_cost_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.cost_value_attributes.clear() props.active_cost_value_id = cost_value.id() props.cost_value_editing_type = "ATTRIBUTES" @classmethod def get_resource_cost_value_formula(cls): - return bpy.context.scene.BIMResourceProperties.cost_value_formula + props = cls.get_resource_props() + return props.cost_value_formula @classmethod def get_resource_cost_value_attributes(cls) -> dict[str, Any]: - def callback(attributes, prop): + props = cls.get_resource_props() + + def callback(data: dict[str, Any], prop: Attribute) -> bool: if prop.name == "UnitBasisValue": if prop.is_null: - attributes["UnitBasis"] = None + data["UnitBasis"] = None return True - attributes["UnitBasis"] = { + data["UnitBasis"] = { "ValueComponent": prop.float_value or 1, - "UnitComponent": tool.Ifc.get().by_id( - int( - bpy.context.scene.BIMResourceProperties.cost_value_attributes.get( - "UnitBasisUnit" - ).enum_value - ) - ), + "UnitComponent": tool.Ifc.get().by_id(int(props.cost_value_attributes["UnitBasisUnit"].enum_value)), } return True if prop.name == "UnitBasisUnit": return True + return False - return bonsai.bim.helper.export_attributes( - bpy.context.scene.BIMResourceProperties.cost_value_attributes, callback - ) + return bonsai.bim.helper.export_attributes(props.cost_value_attributes, callback) @classmethod def enable_editing_resource_base_quantity(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.active_resource_id = resource.id() props.editing_resource_type = "QUANTITY" props.active_resource_class = resource.is_a() @@ -283,22 +288,24 @@ class Resource(bonsai.core.tool.Resource): @classmethod def enable_editing_resource_quantity(cls, resource_quantity: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() props.quantity_attributes.clear() props.is_editing_quantity = True bonsai.bim.helper.import_attributes(resource_quantity, props.quantity_attributes) @classmethod def disable_editing_resource_quantity(cls) -> None: - bpy.context.scene.BIMResourceProperties.is_editing_quantity = False + props = cls.get_resource_props() + props.property_unset("is_editing_quantity") @classmethod def get_resource_quantity_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.quantity_attributes) + props = cls.get_resource_props() + return bonsai.bim.helper.export_attributes(props.quantity_attributes) @classmethod def expand_resource(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() contracted_resources = json.loads(props.contracted_resources) if not resource.id() in contracted_resources: return @@ -307,7 +314,7 @@ class Resource(bonsai.core.tool.Resource): @classmethod def contract_resource(cls, resource: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMResourceProperties + props = cls.get_resource_props() contracted_resources = json.loads(props.contracted_resources) contracted_resources.append(resource.id()) props.contracted_resources = json.dumps(contracted_resources) @@ -332,16 +339,13 @@ class Resource(bonsai.core.tool.Resource): @classmethod def get_highlighted_resource(cls) -> Union[ifcopenshell.entity_instance, None]: - resources = len(bpy.context.scene.BIMResourceTreeProperties.resources) - if resources and resources > bpy.context.scene.BIMResourceProperties.active_resource_index: - return tool.Ifc.get().by_id( - bpy.context.scene.BIMResourceTreeProperties.resources[ - bpy.context.scene.BIMResourceProperties.active_resource_index - ].ifc_definition_id - ) + props = cls.get_resource_props() + active_resource = props.active_resource + if active_resource: + return tool.Ifc.get().by_id(active_resource.ifc_definition_id) @classmethod - def clear_productivity_data(cls, props: bpy.types.PropertyGroup) -> None: + def clear_productivity_data(cls, props: BIMResourceProductivity) -> None: for duration_prop in props.quantity_consumed or []: if duration_prop.name == "BaseQuantityConsumed": duration_prop.years = 0 @@ -356,22 +360,21 @@ class Resource(bonsai.core.tool.Resource): @classmethod def load_productivity_data(cls) -> None: duration_props = None - for collection_prop in bpy.context.scene.BIMResourceProductivity.quantity_consumed: + productivity_props = tool.Resource.get_resource_props().productivity + for collection_prop in productivity_props.quantity_consumed: duration_props = collection_prop if collection_prop.name == "BaseQuantityConsumed" else None break if not duration_props: - duration_props = bpy.context.scene.BIMResourceProductivity.quantity_consumed.add() + duration_props = productivity_props.quantity_consumed.add() duration_props.name = "BaseQuantityConsumed" - cls.clear_productivity_data(bpy.context.scene.BIMResourceProductivity) + cls.clear_productivity_data(productivity_props) current_resource = tool.Resource.get_highlighted_resource() if current_resource: productivity = cls.get_productivity(current_resource) if productivity: - bpy.context.scene.BIMResourceProductivity.quantity_produced = ( - ifcopenshell.util.resource.get_quantity_produced(productivity) - ) - bpy.context.scene.BIMResourceProductivity.quantity_produced_name = ( - ifcopenshell.util.resource.get_quantity_produced_name(productivity) + productivity_props.quantity_produced = ifcopenshell.util.resource.get_quantity_produced(productivity) + productivity_props.quantity_produced_name = ifcopenshell.util.resource.get_quantity_produced_name( + productivity ) time_consumed = ifcopenshell.util.resource.get_unit_consumed(productivity) if time_consumed: @@ -387,7 +390,7 @@ class Resource(bonsai.core.tool.Resource): @classmethod def get_productivity_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMResourceProductivity + props = tool.Resource.get_resource_props().productivity productivity = {} if props.quantity_consumed: import bonsai.bim.module.sequence.helper as helper @@ -446,24 +449,24 @@ class Resource(bonsai.core.tool.Resource): @classmethod def go_to_resource(cls, resource: ifcopenshell.entity_instance) -> None: - def get_ancestors_ids(resource): - ids = [] + def get_ancestors_ids(resource: ifcopenshell.entity_instance) -> list[int]: + ids: list[int] = [] for rel in resource.Nests or []: ids.append(rel.RelatingObject.id()) ids.extend(get_ancestors_ids(rel.RelatingObject)) return ids + props = cls.get_resource_props() ancestors = get_ancestors_ids(resource) - contracted_resources = json.loads(bpy.context.scene.BIMResourceProperties.contracted_resources) + contracted_resources = json.loads(props.contracted_resources) for ancestor in ancestors: if ancestor in contracted_resources: contracted_resources.remove(ancestor) - bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources) + props.contracted_resources = json.dumps(contracted_resources) cls.load_resources() - resource_props = bpy.context.scene.BIMResourceTreeProperties - expanded_resources = [item.ifc_definition_id for item in resource_props.resources] - bpy.context.scene.BIMResourceProperties.active_resource_index = expanded_resources.index(resource.id()) + expanded_resources = [item.ifc_definition_id for item in props.tree.resources] + props.active_resource_index = expanded_resources.index(resource.id()) @classmethod def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None: