From d35a2dcf5b18c98c4827cdef6436c59340119c36 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 25 Mar 2025 12:05:55 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/bim/helper.py | 4 +- .../bonsai/bim/module/classification/data.py | 7 +- .../bonsai/bim/module/classification/ui.py | 3 +- src/bonsai/bonsai/bim/module/cost/data.py | 17 +- src/bonsai/bonsai/bim/module/cost/operator.py | 14 +- src/bonsai/bonsai/bim/module/cost/prop.py | 85 +++++- src/bonsai/bonsai/bim/module/cost/ui.py | 50 ++-- src/bonsai/bonsai/bim/module/csv/prop.py | 1 + .../bonsai/bim/module/material/operator.py | 7 +- src/bonsai/bonsai/bim/module/pset/data.py | 5 +- src/bonsai/bonsai/bim/module/pset/ui.py | 7 +- src/bonsai/bonsai/bim/module/sequence/data.py | 17 +- .../bonsai/bim/module/sequence/operator.py | 30 +- src/bonsai/bonsai/bim/module/sequence/prop.py | 263 ++++++++++++++---- src/bonsai/bonsai/bim/module/sequence/ui.py | 50 +++- src/bonsai/bonsai/tool/blender.py | 11 +- src/bonsai/bonsai/tool/cost.py | 182 +++++++----- src/bonsai/bonsai/tool/sequence.py | 234 +++++++++------- src/ifccsv/ifccsv.py | 10 +- .../api/material/assign_material.py | 6 - .../api/material/remove_material_set.py | 2 + .../api/material/unassign_material.py | 12 +- 22 files changed, 688 insertions(+), 329 deletions(-) diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index 147eb424b2..12c5ef259b 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -76,8 +76,8 @@ def draw_attribute( elif value_name == "filepath_value": attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name) elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"): - propis = bpy.context.scene.BIMWorkScheduleProperties - for item in propis.durations_attributes: + props = tool.Sequence.get_work_schedule_props() + for item in props.durations_attributes: if item.name == attribute.name: duration_props = item layout.label(text=attribute.name) diff --git a/src/bonsai/bonsai/bim/module/classification/data.py b/src/bonsai/bonsai/bim/module/classification/data.py index e4c5e30ca8..35bf6900ec 100644 --- a/src/bonsai/bonsai/bim/module/classification/data.py +++ b/src/bonsai/bonsai/bim/module/classification/data.py @@ -145,11 +145,8 @@ class CostClassificationsData(ReferencesData): @classmethod def references(cls): results = [] - element = tool.Ifc.get().by_id( - bpy.context.scene.BIMCostProperties.cost_items[ - bpy.context.scene.BIMCostProperties.active_cost_item_index - ].ifc_definition_id - ) + props = tool.Cost.get_cost_props() + element = tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id) if element: for reference in ifcopenshell.util.classification.get_references(element): data = reference.get_info() diff --git a/src/bonsai/bonsai/bim/module/classification/ui.py b/src/bonsai/bonsai/bim/module/classification/ui.py index c171ff3cd6..4b9081c3a8 100644 --- a/src/bonsai/bonsai/bim/module/classification/ui.py +++ b/src/bonsai/bonsai/bim/module/classification/ui.py @@ -338,7 +338,8 @@ class BIM_PT_cost_classifications(Panel, ReferenceUI): def poll(cls, context): if not tool.Ifc.get(): return False - return bool(context.scene.BIMCostProperties.cost_items) + props = tool.Cost.get_cost_props() + return bool(props.cost_items) def draw(self, context): if not CostClassificationsData.is_loaded: diff --git a/src/bonsai/bonsai/bim/module/cost/data.py b/src/bonsai/bonsai/bim/module/cost/data.py index cb4dc26a71..f1cb4d93c0 100644 --- a/src/bonsai/bonsai/bim/module/cost/data.py +++ b/src/bonsai/bonsai/bim/module/cost/data.py @@ -66,8 +66,9 @@ class CostSchedulesData: @classmethod def schedules(cls): results = [] - if bpy.context.scene.BIMCostProperties.active_cost_schedule_id: - schedule = tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id) + props = tool.Cost.get_cost_props() + if props.active_cost_schedule_id: + schedule = tool.Ifc.get().by_id(props.active_cost_schedule_id) results.append( { "id": schedule.id(), @@ -88,7 +89,8 @@ class CostSchedulesData: @classmethod def is_editing_rates(cls): - ifc_id = bpy.context.scene.BIMCostProperties.active_cost_schedule_id + props = tool.Cost.get_cost_props() + ifc_id = props.active_cost_schedule_id if not ifc_id: return return tool.Ifc.get().by_id(ifc_id).PredefinedType == "SCHEDULEOFRATES" @@ -256,7 +258,8 @@ class CostSchedulesData: @classmethod def cost_quantities(cls): results = [] - ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id + props = tool.Cost.get_cost_props() + ifc_id = props.active_cost_item_id if not ifc_id: return results for quantity in tool.Ifc.get().by_id(ifc_id).CostQuantities or []: @@ -265,7 +268,8 @@ class CostSchedulesData: @classmethod def cost_values(cls): - ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id + props = tool.Cost.get_cost_props() + ifc_id = props.active_cost_item_id if not ifc_id: return [] return ifcopenshell.util.cost.get_cost_values(tool.Ifc.get().by_id(ifc_id)) @@ -324,7 +328,8 @@ class CostItemQuantitiesData: @classmethod def process_quantity_names(cls): - active_task_index = bpy.context.scene.BIMWorkScheduleProperties.active_task_index + props = tool.Sequence.get_work_schedule_props() + active_task_index = props.active_task_index tprops = tool.Sequence.get_task_tree_props() total_tasks = len(tprops.tasks) if not total_tasks or active_task_index >= total_tasks: diff --git a/src/bonsai/bonsai/bim/module/cost/operator.py b/src/bonsai/bonsai/bim/module/cost/operator.py index 3cbe9b4d68..79338d5ede 100644 --- a/src/bonsai/bonsai/bim/module/cost/operator.py +++ b/src/bonsai/bonsai/bim/module/cost/operator.py @@ -36,16 +36,18 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator): object_type: bpy.props.StringProperty() def _execute(self, context): - predefined_type = context.scene.BIMCostProperties.cost_schedule_predefined_types + props = tool.Cost.get_cost_props() + predefined_type = props.cost_schedule_predefined_types if predefined_type == "USERDEFINED": predefined_type = self.object_type core.add_cost_schedule(tool.Ifc, name=self.name, predefined_type=predefined_type) def draw(self, context): layout = self.layout + props = tool.Cost.get_cost_props() layout.prop(self, "name", text="Name") - layout.prop(context.scene.BIMCostProperties, "cost_schedule_predefined_types", text="Type") - if context.scene.BIMCostProperties.cost_schedule_predefined_types == "USERDEFINED": + layout.prop(props, "cost_schedule_predefined_types", text="Type") + if props.cost_schedule_predefined_types == "USERDEFINED": layout.prop(self, "object_type", text="Object type") def invoke(self, context, event): @@ -58,10 +60,11 @@ class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Cost.get_cost_props() core.edit_cost_schedule( tool.Ifc, tool.Cost, - cost_schedule=tool.Ifc.get().by_id(context.scene.BIMCostProperties.active_cost_schedule_id), + cost_schedule=tool.Ifc.get().by_id(props.active_cost_schedule_id), ) @@ -559,7 +562,8 @@ class AddCostColumn(bpy.types.Operator): @classmethod def poll(cls, context): - if not context.scene.BIMCostProperties.cost_column: + props = tool.Cost.get_cost_props() + if not props.cost_column: cls.poll_message_set("Cost column name is empty") return False return True diff --git a/src/bonsai/bonsai/bim/module/cost/prop.py b/src/bonsai/bonsai/bim/module/cost/prop.py index abe035fdb6..1ce39c6f1a 100644 --- a/src/bonsai/bonsai/bim/module/cost/prop.py +++ b/src/bonsai/bonsai/bim/module/cost/prop.py @@ -33,6 +33,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING, Literal def get_schedule_of_rates(self, context): @@ -78,8 +79,8 @@ def update_active_cost_item_index(self, context): CostClassificationsData.load() -def update_cost_item_identification(self, context): - props = context.scene.BIMCostProperties +def update_cost_item_identification(self: "CostItem", context: bpy.types.Context): + props = tool.Cost.get_cost_props() if not props.is_cost_update_enabled or self.identification == "XXX": return self.file = tool.Ifc.get() @@ -93,8 +94,8 @@ def update_cost_item_identification(self, context): attribute.string_value = self.identification -def update_cost_item_name(self, context): - props = context.scene.BIMCostProperties +def update_cost_item_name(self: "CostItem", context: bpy.types.Context) -> None: + props = tool.Cost.get_cost_props() if not props.is_cost_update_enabled or self.name == "Unnamed": return self.file = tool.Ifc.get() @@ -142,6 +143,14 @@ class CostItem(PropertyGroup): is_expanded: BoolProperty(name="Is Expanded") level_index: IntProperty(name="Level Index") + if TYPE_CHECKING: + name: str + identification: str + ifc_definition_id: int + has_children: bool + is_expanded: bool + level_index: int + class CostItemQuantity(PropertyGroup): name: StringProperty(name="Name") @@ -150,11 +159,22 @@ class CostItemQuantity(PropertyGroup): unit_symbol: StringProperty(name="Unit Symbol") total_cost_quantity: FloatProperty(name="Total Quantity") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + total_quantity: float + unit_symbol: str + total_cost_quantity: float + class CostItemType(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + def update_cost_item_parent(self, context): cost_item = tool.Cost.get_highlighted_cost_item() @@ -176,6 +196,9 @@ def update_active_cost_item_resources(self, context): class ScheduleColumn(PropertyGroup): schedule_id: IntProperty() + if TYPE_CHECKING: + schedule_id: int + class BIMCostProperties(PropertyGroup): cost_schedule_predefined_types: EnumProperty( @@ -249,3 +272,57 @@ class BIMCostProperties(PropertyGroup): custom_currency: StringProperty( name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format" ) + + if TYPE_CHECKING: + cost_schedule_predefined_types: str + is_cost_update_enabled: bool + cost_schedule_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + is_editing: str + active_cost_schedule_id: int + cost_items: bpy.types.bpy_prop_collection_idprop[CostItem] + active_cost_item_id: int + cost_item_editing_type: str + active_cost_item_index: int + cost_item_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + contracted_cost_items: str + quantity_types: str + product_quantity_names: str + process_quantity_names: str + resource_quantity_names: str + active_cost_item_quantity_id: int + quantity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + cost_types: Literal["FIXED", "SUM", "CATEGORY"] + cost_category: str + fixed_cost_value: float + active_cost_value_id: int + cost_value_editing_type: str + cost_value_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + cost_value_formula: str + cost_column: str + should_show_column_ui: bool + should_show_currency_ui: bool + columns: bpy.types.bpy_prop_collection_idprop[StrProperty] + columns_storage: bpy.types.bpy_prop_collection_idprop[ScheduleColumn] + active_column_index: int + cost_item_products: bpy.types.bpy_prop_collection_idprop[CostItemQuantity] + active_cost_item_product_index: int + cost_item_processes: bpy.types.bpy_prop_collection_idprop[CostItemQuantity] + active_cost_item_process_index: int + cost_item_resources: bpy.types.bpy_prop_collection_idprop[CostItemQuantity] + active_cost_item_resource_index: int + cost_item_type_products: bpy.types.bpy_prop_collection_idprop[CostItemType] + active_cost_item_type_product_index: int + schedule_of_rates: str + cost_item_rates: bpy.types.bpy_prop_collection_idprop[CostItem] + active_cost_item_rate_index: int + contracted_cost_item_rates: str + product_cost_items: bpy.types.bpy_prop_collection_idprop[CostItemQuantity] + active_product_cost_item_index: int + enable_reorder: bool + show_nested_elements: bool + show_nested_tasks: bool + show_nested_resources: bool + change_cost_item_parent: bool + show_cost_item_operators: bool + currency: str + custom_currency: str diff --git a/src/bonsai/bonsai/bim/module/cost/ui.py b/src/bonsai/bonsai/bim/module/cost/ui.py index e8df647071..6897e636cc 100644 --- a/src/bonsai/bonsai/bim/module/cost/ui.py +++ b/src/bonsai/bonsai/bim/module/cost/ui.py @@ -16,13 +16,17 @@ # 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.bim.helper import bonsai.bim.module.cost.prop as CostProp import bonsai.tool as tool from bpy.types import Panel, UIList from bonsai.bim.module.cost.data import CostSchedulesData -from typing import Any +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity class BIM_PT_cost_schedules(Panel): @@ -43,7 +47,7 @@ class BIM_PT_cost_schedules(Panel): if not CostSchedulesData.is_loaded: CostSchedulesData.load() - self.props = context.scene.BIMCostProperties + self.props = tool.Cost.get_cost_props() if not self.props.active_cost_schedule_id: row = self.layout.row(align=True) if CostSchedulesData.data["total_cost_schedules"]: @@ -299,7 +303,7 @@ class BIM_PT_cost_item_types(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMCostProperties + props = tool.Cost.get_cost_props() total_cost_items = len(props.cost_items) if not props.active_cost_schedule_id: return False @@ -312,7 +316,7 @@ class BIM_PT_cost_item_types(Panel): return False def draw(self, context): - self.props = context.scene.BIMCostProperties + self.props = tool.Cost.get_cost_props() cost_item = self.props.cost_items[self.props.active_cost_item_index] grid = self.layout.grid_flow(columns=3, even_columns=True) @@ -385,7 +389,7 @@ class BIM_PT_cost_item_quantities(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMCostProperties + props = tool.Cost.get_cost_props() total_cost_items = len(props.cost_items) if not props.active_cost_schedule_id: return False @@ -398,7 +402,7 @@ class BIM_PT_cost_item_quantities(Panel): return False def draw(self, context): - self.props = context.scene.BIMCostProperties + self.props = tool.Cost.get_cost_props() cost_item = self.props.cost_items[self.props.active_cost_item_index] @@ -462,7 +466,7 @@ class BIM_PT_cost_item_quantities(Panel): row2.label(text="Tasks ({})".format(total_cost_item_processes)) tprops = tool.Sequence.get_task_tree_props() - wprops = context.scene.BIMWorkScheduleProperties + wprops = tool.Sequence.get_work_schedule_props() if tprops.tasks and wprops.active_task_index < len(tprops.tasks): if has_quantity_names: op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") @@ -557,7 +561,7 @@ class BIM_PT_cost_item_rates(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMCostProperties + props = tool.Cost.get_cost_props() total_cost_items = len(props.cost_items) if not props.active_cost_schedule_id: return False @@ -570,7 +574,7 @@ class BIM_PT_cost_item_rates(Panel): return False def draw(self, context): - self.props = context.scene.BIMCostProperties + self.props = tool.Cost.get_cost_props() row = self.layout.row(align=True) row.prop(self.props, "schedule_of_rates", text="") if self.props.active_cost_item_rate_index < len(self.props.cost_item_rates): @@ -606,13 +610,14 @@ class BIM_UL_cost_items_trait: split2.label(text="Quantity") split2.label(text="Value") - for column in bpy.context.scene.BIMCostProperties.columns: + props = tool.Cost.get_cost_props() + for column in props.columns: split2.label(text=column.name) split2.label(text="Total Cost") def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: - self.props = context.scene.BIMCostProperties + self.props = tool.Cost.get_cost_props() cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id] row = layout.row(align=True) @@ -728,7 +733,6 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): class BIM_UL_cost_columns(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = context.scene.BIMCostProperties if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") @@ -737,7 +741,7 @@ class BIM_UL_cost_columns(UIList): class BIM_UL_cost_item_types(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = context.scene.BIMCostProperties + props = tool.Cost.get_cost_props() cost_item = props.cost_items[props.active_cost_item_index] if item: @@ -749,9 +753,17 @@ class BIM_UL_cost_item_types(UIList): class BIM_UL_cost_item_quantities(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = context.scene.BIMCostProperties - cost_item = props.cost_items[props.active_cost_item_index] + def draw_item( + self, + context: bpy.types.Context, + layout: bpy.types.UILayout, + data: BIMCostProperties, + item: CostItemQuantity, + icon, + active_data, + active_propname, + ): + cost_item = data.cost_items[data.active_cost_item_index] if item: row = layout.row(align=True) op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF") @@ -788,15 +800,15 @@ class BIM_PT_Costing_Tools(Panel): bl_parent_id = "BIM_PT_tab_cost" def draw(self, context): - self.props = context.scene.BIMCostProperties + props = tool.Cost.get_cost_props() row = self.layout.row() row.operator("bim.load_product_cost_items", icon="FILE_REFRESH") row = self.layout.row() row.template_list( "BIM_UL_product_cost_items", "", - self.props, + props, "product_cost_items", - self.props, + props, "active_product_cost_item_index", ) diff --git a/src/bonsai/bonsai/bim/module/csv/prop.py b/src/bonsai/bonsai/bim/module/csv/prop.py index f66ee75c51..371776bef2 100644 --- a/src/bonsai/bonsai/bim/module/csv/prop.py +++ b/src/bonsai/bonsai/bim/module/csv/prop.py @@ -61,6 +61,7 @@ class CsvAttribute(PropertyGroup): formatting: StringProperty(default="{{value}}", name="Formatting") if TYPE_CHECKING: + name: str header: str sort: Literal["NONE", "ASC", "DESC"] group: Literal["NONE", "GROUP", "CONCAT", "VARIES", "SUM", "AVERAGE", "MIN", "MAX"] diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 5562f4a3e3..12f71adbff 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -281,9 +281,12 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() + if TYPE_CHECKING: + obj: str + def _execute(self, context): - objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects() - core.unassign_material(tool.Ifc, tool.Material, objects=objects) + objects = [bpy.data.objects[self.obj]] if self.obj else tool.Blender.get_selected_objects() + core.unassign_material(tool.Ifc, tool.Material, objects=list(objects)) class AddConstituent(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py index 0c015f784a..09d63b41a5 100644 --- a/src/bonsai/bonsai/bim/module/pset/data.py +++ b/src/bonsai/bonsai/bim/module/pset/data.py @@ -227,7 +227,7 @@ class TaskQtosData(Data): @classmethod def load(cls): - wprops = bpy.context.scene.BIMWorkScheduleProperties + wprops = tool.Sequence.get_work_schedule_props() 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)} @@ -311,7 +311,8 @@ class WorkSchedulePsetsData(Data): @classmethod def load(cls): - ifc_definition_id = bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id + props = tool.Sequence.get_work_schedule_props() + ifc_definition_id = props.active_work_schedule_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/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index 62fd6366c9..19d275430f 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -494,7 +494,7 @@ class BIM_PT_task_qtos(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMWorkScheduleProperties + props = tool.Sequence.get_work_schedule_props() if not props.active_work_schedule_id: return False tprops = tool.Sequence.get_task_tree_props() @@ -712,9 +712,8 @@ class BIM_PT_work_schedule_psets(Panel): @classmethod def poll(cls, context): - if not context.scene.BIMWorkScheduleProperties.active_work_schedule_id: - return False - return True + props = tool.Sequence.get_work_schedule_props() + return bool(props.active_work_schedule_id) def draw(self, context): if not WorkSchedulePsetsData.is_loaded: diff --git a/src/bonsai/bonsai/bim/module/sequence/data.py b/src/bonsai/bonsai/bim/module/sequence/data.py index be7f19f28b..512056f1b3 100644 --- a/src/bonsai/bonsai/bim/module/sequence/data.py +++ b/src/bonsai/bonsai/bim/module/sequence/data.py @@ -307,20 +307,19 @@ class WorkScheduleData: cls.is_loaded = True @classmethod - def can_have_baselines(cls): - if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + def can_have_baselines(cls) -> bool: + props = tool.Sequence.get_work_schedule_props() + if not props.active_work_schedule_id: return False - return ( - tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id).PredefinedType - == "PLANNED" - ) + return tool.Ifc.get().by_id(props.active_work_schedule_id).PredefinedType == "PLANNED" @classmethod - def active_work_schedule_baselines(cls): + def active_work_schedule_baselines(cls) -> list[dict[str, Any]]: results = [] - if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + props = tool.Sequence.get_work_schedule_props() + if not props.active_work_schedule_id: return [] - for rel in tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id).Declares: + for rel in tool.Ifc.get().by_id(props.active_work_schedule_id).Declares: for work_schedule in rel.RelatedObjects: if work_schedule.PredefinedType == "BASELINE": results.append( diff --git a/src/bonsai/bonsai/bim/module/sequence/operator.py b/src/bonsai/bonsai/bim/module/sequence/operator.py index 98e7ae6bce..dcd80c78e7 100644 --- a/src/bonsai/bonsai/bim/module/sequence/operator.py +++ b/src/bonsai/bonsai/bim/module/sequence/operator.py @@ -232,7 +232,7 @@ class AddWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): def draw(self, context): layout = self.layout layout.prop(self, "name", text="Name") - self.props = context.scene.BIMWorkScheduleProperties + self.props = tool.Sequence.get_work_schedule_props() layout.prop(self.props, "work_schedule_predefined_types", text="Type") if self.props.work_schedule_predefined_types == "USERDEFINED": layout.prop(self.props, "object_type", text="Object type") @@ -247,10 +247,11 @@ class EditWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Sequence.get_work_schedule_props() core.edit_work_schedule( tool.Ifc, tool.Sequence, - work_schedule=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_work_schedule_id), + work_schedule=tool.Ifc.get().by_id(props.active_work_schedule_id), ) @@ -376,11 +377,12 @@ class EditTaskTime(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Sequence.get_work_schedule_props() core.edit_task_time( tool.Ifc, tool.Sequence, tool.Resource, - task_time=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_task_time_id), + task_time=tool.Ifc.get().by_id(props.active_task_time_id), ) @@ -411,9 +413,8 @@ class EditTask(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_task( - tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_task_id) - ) + props = tool.Sequence.get_work_schedule_props() + core.edit_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(props.active_task_id)) class CopyTaskAttribute(bpy.types.Operator, tool.Ifc.Operator): @@ -1083,10 +1084,11 @@ class EditSequenceAttributes(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + props = tool.Sequence.get_work_schedule_props() core.edit_sequence_attributes( tool.Ifc, tool.Sequence, - rel_sequence=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_sequence_id), + rel_sequence=tool.Ifc.get().by_id(props.active_sequence_id), ) @@ -1138,7 +1140,8 @@ class VisualiseWorkScheduleDate(bpy.types.Operator): @classmethod def poll(cls, context): - return bool(bpy.context.scene.BIMWorkScheduleProperties.visualisation_start) + props = tool.Sequence.get_work_schedule_props() + return bool(props.visualisation_start) def execute(self, context): core.visualise_work_schedule_date(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) @@ -1163,10 +1166,8 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): @classmethod def poll(cls, context): - has_start, has_finish = ( - bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, - bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish, - ) + props = tool.Sequence.get_work_schedule_props() + has_start, has_finish = props.visualisation_start, props.visualisation_finish return bool(has_start and has_finish) and not "-" in (has_start, has_finish) def execute(self, context): @@ -1421,11 +1422,12 @@ class LoadAnimationColorScheme(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Loads the animation color scheme" def _execute(self, context): - group = tool.Ifc.get().by_id(int(context.scene.BIMAnimationProperties.saved_color_schemes)) + props = tool.Sequence.get_animation_props() + group = tool.Ifc.get().by_id(int(props.saved_color_schemes)) core.load_animation_color_scheme(tool.Sequence, scheme=group) def draw(self, context): - props = context.scene.BIMAnimationProperties + props = tool.Sequence.get_animation_props() row = self.layout.row() row.prop(props, "saved_color_schemes", text="") diff --git a/src/bonsai/bonsai/bim/module/sequence/prop.py b/src/bonsai/bonsai/bim/module/sequence/prop.py index a63a4928c0..7f305ab49e 100644 --- a/src/bonsai/bonsai/bim/module/sequence/prop.py +++ b/src/bonsai/bonsai/bim/module/sequence/prop.py @@ -19,6 +19,7 @@ import bpy import isodate import ifcopenshell.api +import ifcopenshell.api.sequence import ifcopenshell.util.attribute import ifcopenshell.util.date import bonsai.tool as tool @@ -26,6 +27,7 @@ import bonsai.core.sequence as core from bonsai.bim.module.sequence.data import SequenceData, AnimationColorSchemeData, refresh as refresh_sequence_data import bonsai.bim.module.resource.data import bonsai.bim.module.pset.data +from mathutils import Color from bonsai.bim.prop import StrProperty, Attribute from dateutil import parser from bpy.types import PropertyGroup @@ -39,7 +41,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal def getTaskColumns(self, context): @@ -93,48 +95,48 @@ def update_active_task_inputs(self, context): tool.Sequence.load_task_inputs(inputs) -def updateTaskName(self, context): - props = context.scene.BIMWorkScheduleProperties +def updateTaskName(self: "Task", context: bpy.types.Context) -> None: + props = tool.Sequence.get_work_schedule_props() if not props.is_task_update_enabled or self.name == "Unnamed": return - self.file = tool.Ifc.get() - ifcopenshell.api.run( - "sequence.edit_task", - self.file, - **{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}}, + ifc_file = tool.Ifc.get() + ifcopenshell.api.sequence.edit_task( + ifc_file, + task=ifc_file.by_id(self.ifc_definition_id), + attributes={"Name": self.name}, ) SequenceData.load() if props.active_task_id == self.ifc_definition_id: - attribute = props.task_attributes.get("Name") + attribute = props.task_attributes["Name"] attribute.string_value = self.name -def updateTaskIdentification(self, context): - props = context.scene.BIMWorkScheduleProperties +def updateTaskIdentification(self: "Task", context: bpy.types.Context) -> None: + props = tool.Sequence.get_work_schedule_props() if not props.is_task_update_enabled or self.identification == "XXX": return - self.file = tool.Ifc.get() - ifcopenshell.api.run( - "sequence.edit_task", - self.file, - **{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}}, + ifc_file = tool.Ifc.get() + ifcopenshell.api.sequence.edit_task( + ifc_file, + task=ifc_file.by_id(self.ifc_definition_id), + attributes={"Identification": self.identification}, ) SequenceData.load() if props.active_task_id == self.ifc_definition_id: - attribute = props.task_attributes.get("Identification") + attribute = props.task_attributes["Identification"] attribute.string_value = self.identification -def updateTaskTimeStart(self, context): +def updateTaskTimeStart(self: "Task", context: bpy.types.Context) -> None: updateTaskTimeDateTime(self, context, "start") -def updateTaskTimeFinish(self, context): +def updateTaskTimeFinish(self: "Task", context: bpy.types.Context) -> None: updateTaskTimeDateTime(self, context, "finish") -def updateTaskTimeDateTime(self, context, startfinish): - props = context.scene.BIMWorkScheduleProperties +def updateTaskTimeDateTime(self: "Task", context: bpy.types.Context, startfinish: Literal["start", "finish"]) -> None: + props = tool.Sequence.get_work_schedule_props() if not props.is_task_update_enabled: return @@ -149,7 +151,7 @@ def updateTaskTimeDateTime(self, context, startfinish): if startfinish_value == "-": return - self.file = tool.Ifc.get() + ifc_file = tool.Ifc.get() try: startfinish_datetime = parser.isoparse(startfinish_value) @@ -160,11 +162,11 @@ def updateTaskTimeDateTime(self, context, startfinish): setattr(self, startfinish, "-") return - task = self.file.by_id(self.ifc_definition_id) + task = ifc_file.by_id(self.ifc_definition_id) if task.TaskTime: task_time = task.TaskTime else: - task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task) + task_time = ifcopenshell.api.sequence.add_task_time(ifc_file, task=task) SequenceData.load() startfinish_key = "Schedule" + startfinish.capitalize() @@ -174,17 +176,17 @@ def updateTaskTimeDateTime(self, context, startfinish): setattr(self, startfinish, canonical_startfinish_value) return - ifcopenshell.api.run( - "sequence.edit_task_time", - self.file, - **{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}}, + ifcopenshell.api.sequence.edit_task_time( + ifc_file, + task_time=task_time, + attributes={startfinish_key: startfinish_datetime}, ) SequenceData.load() bpy.ops.bim.load_task_properties() -def updateTaskDuration(self, context): - props = context.scene.BIMWorkScheduleProperties +def updateTaskDuration(self: "Task", context: bpy.types.Context) -> None: + props = tool.Sequence.get_work_schedule_props() if not props.is_task_update_enabled: return @@ -196,12 +198,17 @@ def updateTaskDuration(self, context): self.duration = "-" return - task = tool.Ifc.get().by_id(self.ifc_definition_id) + ifc_file = tool.Ifc.get() + task = ifc_file.by_id(self.ifc_definition_id) if task.TaskTime: task_time = task.TaskTime else: - task_time = tool.Ifc.run("sequence.add_task_time", task=task) - tool.Ifc.run("sequence.edit_task_time", task_time=task_time, attributes={"ScheduleDuration": duration}) + task_time = ifcopenshell.api.sequence.add_task_time(ifc_file, task=task) + ifcopenshell.api.sequence.edit_task_time( + ifc_file, + task_time=task_time, + attributes={"ScheduleDuration": duration}, + ) core.load_task_properties(tool.Sequence) tool.Sequence.refresh_task_resources() @@ -212,15 +219,19 @@ def get_schedule_predefined_types(self, context): return SequenceData.data["schedule_predefined_types_enum"] -def update_visualisation_start(self, context): +def update_visualisation_start(self: "BIMWorkScheduleProperties", context: bpy.types.Context) -> None: update_visualisation_start_finish(self, context, "visualisation_start") -def update_visualisation_finish(self, context): +def update_visualisation_finish(self: "BIMWorkScheduleProperties", context: bpy.types.Context) -> None: update_visualisation_start_finish(self, context, "visualisation_finish") -def update_visualisation_start_finish(self, context, startfinish): +def update_visualisation_start_finish( + self: "BIMWorkScheduleProperties", + context: bpy.types.Context, + startfinish: Literal["visualisation_start", "visualisation_finish"], +) -> None: def canonicalise_time(time): if not time: return "-" @@ -243,34 +254,34 @@ def update_visualisation_start_finish(self, context, startfinish): def update_color_full(self, context): material = bpy.data.materials.get("color_full") if material: - color_full = bpy.context.scene.BIMAnimationProperties.color_full + props = tool.Sequence.get_animation_props() inputs = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs color = inputs["Base Color"].default_value - color[0] = color_full.r - color[1] = color_full.g - color[2] = color_full.b + color[0] = props.color_full.r + color[1] = props.color_full.g + color[2] = props.color_full.b def update_color_progress(self, context): material = bpy.data.materials.get("color_progress") if material: - color_progress = bpy.context.scene.BIMAnimationProperties.color_progress + props = tool.Sequence.get_animation_props() inputs = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs color = inputs["Base Color"].default_value - color[0] = color_progress.r - color[1] = color_progress.g - color[2] = color_progress.b + color[0] = props.color_progress.r + color[1] = props.color_progress.g + color[2] = props.color_progress.b -def update_sort_reversed(self, context): - if context.scene.BIMWorkScheduleProperties.active_work_schedule_id: +def update_sort_reversed(self: "BIMWorkScheduleProperties", context: bpy.types.Context) -> None: + if self.active_work_schedule_id: core.load_task_tree( tool.Sequence, - work_schedule=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_work_schedule_id), + work_schedule=tool.Ifc.get().by_id(self.active_work_schedule_id), ) -def update_filter_by_active_schedule(self, context): +def update_filter_by_active_schedule(self: "BIMWorkScheduleProperties", context: bpy.types.Context) -> None: if obj := context.active_object: product = tool.Ifc.get_entity(obj) assert product @@ -314,8 +325,9 @@ def updateAssignedResourceUsage(self, context): bonsai.bim.module.pset.data.refresh() -def update_task_bar_list(self, context): - if not context.scene.BIMWorkScheduleProperties.is_task_update_enabled: +def update_task_bar_list(self: "Task", context: bpy.types.Context) -> None: + props = tool.Sequence.get_work_schedule_props() + if not props.is_task_update_enabled: return if self.has_bar_visual: tool.Sequence.add_task_bar(self.ifc_definition_id) @@ -368,17 +380,30 @@ class WorkPlan(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + class TaskResource(PropertyGroup): name: StringProperty(name="Name", update=updateAssignedResourceName) ifc_definition_id: IntProperty(name="IFC Definition ID") schedule_usage: FloatProperty(name="Schedule Usage", update=updateAssignedResourceUsage) + if TYPE_CHECKING: + name: str + ifc_definition_id: int + schedule_usage: float + class TaskProduct(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + class BIMWorkPlanProperties(PropertyGroup): work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute) @@ -388,6 +413,14 @@ class BIMWorkPlanProperties(PropertyGroup): active_work_plan_id: IntProperty(name="Active Work Plan Id") work_schedules: EnumProperty(items=getWorkSchedules, name="Work Schedules") + if TYPE_CHECKING: + work_plan_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + editing_type: str + work_plans: bpy.types.bpy_prop_collection_idprop[WorkPlan] + active_work_plan_index: int + active_work_plan_id: int + work_schedules: str + class ISODuration(PropertyGroup): name: StringProperty(name="Name") @@ -398,16 +431,33 @@ 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 IFCStatus(PropertyGroup): name: StringProperty(name="Name") is_visible: BoolProperty(name="Is Visible", default=True, update=lambda x, y: bpy.ops.bim.activate_status_filters()) + if TYPE_CHECKING: + name: str + is_visible: bool + class BIMStatusProperties(PropertyGroup): is_enabled: BoolProperty(name="Is Enabled") statuses: CollectionProperty(name="Statuses", type=IFCStatus) + if TYPE_CHECKING: + is_enabled: bool + statuses: bpy.types.bpy_prop_collection_idprop[IFCStatus] + class BIMWorkScheduleProperties(PropertyGroup): work_schedule_predefined_types: EnumProperty( @@ -493,6 +543,66 @@ class BIMWorkScheduleProperties(PropertyGroup): name="Filter By Active Schedule", default=False, update=update_filter_by_active_schedule ) + if TYPE_CHECKING: + work_schedule_predefined_types: str + object_type: str + durations_attributes: bpy.types.bpy_prop_collection_idprop[ISODuration] + work_calendars: str + work_schedule_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + editing_type: str + editing_task_type: str + active_work_schedule_index: int + active_work_schedule_id: int + active_task_index: int + active_task_id: int + highlighted_task_id: int + task_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + should_show_visualisation_ui: bool + should_show_task_bar_selection: bool + should_show_snapshot_ui: bool + should_show_column_ui: bool + columns: bpy.types.bpy_prop_collection_idprop[Attribute] + active_column_index: int + sort_column: str + is_sort_reversed: bool + column_types: str + task_columns: str + task_time_columns: str + other_columns: str + active_task_time_id: int + task_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + contracted_tasks: str + task_bars: str + is_task_update_enabled: bool + editing_sequence_type: str + active_sequence_id: int + sequence_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + lag_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + visualisation_start: str + visualisation_finish: str + speed_multiplier: float + speed_animation_duration: str + speed_animation_frames: int + speed_real_duration: str + speed_types: str + task_resources: bpy.types.bpy_prop_collection_idprop[TaskResource] + active_task_resource_index: int + task_inputs: bpy.types.bpy_prop_collection_idprop[TaskProduct] + active_task_input_index: int + task_outputs: bpy.types.bpy_prop_collection_idprop[TaskProduct] + active_task_output_index: int + show_nested_outputs: bool + show_nested_resources: bool + show_nested_inputs: bool + product_input_tasks: bpy.types.bpy_prop_collection_idprop[TaskProduct] + product_output_tasks: bpy.types.bpy_prop_collection_idprop[TaskProduct] + active_product_output_task_index: int + active_product_input_task_index: int + enable_reorder: bool + show_task_operators: bool + should_show_schedule_baseline_ui: bool + filter_by_active_schedule: bool + class BIMTaskTreeProperties(PropertyGroup): # This belongs by itself for performance reasons. https://developer.blender.org/T87737 @@ -507,11 +617,19 @@ class WorkCalendar(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + if TYPE_CHECKING: + name: str + ifc_definition_id: int + class RecurrenceComponent(PropertyGroup): name: StringProperty(name="Name") is_specified: BoolProperty(name="Is Specified") + if TYPE_CHECKING: + name: str + is_specified: bool + class BIMWorkCalendarProperties(PropertyGroup): work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute) @@ -542,6 +660,22 @@ class BIMWorkCalendarProperties(PropertyGroup): start_time: StringProperty(name="Start Time") end_time: StringProperty(name="End Time") + if TYPE_CHECKING: + work_calendar_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + work_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + editing_type: str + active_work_calendar_id: int + active_work_time_id: int + day_components: bpy.types.bpy_prop_collection_idprop[RecurrenceComponent] + weekday_components: bpy.types.bpy_prop_collection_idprop[RecurrenceComponent] + month_components: bpy.types.bpy_prop_collection_idprop[RecurrenceComponent] + position: int + interval: int + occurrences: int + recurrence_types: str + start_time: str + end_time: str + def update_selected_date(self: "DatePickerProperties", context: bpy.types.Context) -> None: # `include_time` is `True`, otherwise time props are not displayed in UI. @@ -561,6 +695,13 @@ class DatePickerProperties(PropertyGroup): selected_min: IntProperty(min=0, max=59, update=update_selected_date) selected_sec: IntProperty(min=0, max=59, update=update_selected_date) + if TYPE_CHECKING: + display_date: str + selected_date: str + selected_hour: int + selected_min: int + selected_sec: int + class BIMDateTextProperties(PropertyGroup): start_frame: IntProperty(name="Start Frame") @@ -568,6 +709,12 @@ class BIMDateTextProperties(PropertyGroup): start: StringProperty(name="Start") finish: StringProperty(name="Finish") + if TYPE_CHECKING: + start_frame: int + total_frames: int + start: str + finish: str + class BIMTaskTypeColor(PropertyGroup): name: StringProperty(name="Name") @@ -580,6 +727,11 @@ class BIMTaskTypeColor(PropertyGroup): max=1.0, ) + if TYPE_CHECKING: + name: str + animation_type: str + color: tuple[float, float, float] + class BIMAnimationProperties(PropertyGroup): is_editing: BoolProperty(name="Is Loaded", default=False) @@ -607,3 +759,14 @@ class BIMAnimationProperties(PropertyGroup): update=update_color_progress, ) should_show_task_bar_options: BoolProperty(name="Show Task Bar Options", default=False) + + if TYPE_CHECKING: + is_editing: bool + saved_color_schemes: str + active_color_component_outputs_index: int + active_color_component_inputs_index: int + task_input_colors: bpy.types.bpy_prop_collection_idprop[BIMTaskTypeColor] + task_output_colors: bpy.types.bpy_prop_collection_idprop[BIMTaskTypeColor] + color_full: Color + color_progress: Color + should_show_task_bar_options: bool diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index 21ed962852..3edffe170e 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -30,7 +30,11 @@ from bonsai.bim.module.sequence.data import ( TaskICOMData, AnimationColorSchemeData, ) -from typing import Any, Optional +from typing import Any, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.prop import Attribute + from bonsai.bim.module.sequence.prop import BIMWorkScheduleProperties, BIMTaskTreeProperties, Task class BIM_PT_status(Panel): @@ -154,7 +158,7 @@ class BIM_PT_work_schedules(Panel): SequenceData.load() if not WorkScheduleData.is_loaded: WorkScheduleData.load() - self.props = context.scene.BIMWorkScheduleProperties + self.props = tool.Sequence.get_work_schedule_props() self.tprops = tool.Sequence.get_task_tree_props() if not self.props.active_work_schedule_id: @@ -479,14 +483,14 @@ class BIM_PT_animation_tools(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMWorkScheduleProperties + props = tool.Sequence.get_work_schedule_props() if props.active_work_schedule_id: return True return False def draw(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.animation_props = context.scene.BIMAnimationProperties + self.props = tool.Sequence.get_work_schedule_props() + self.animation_props = tool.Sequence.get_animation_props() row = self.layout.row(align=True) row.alignment = "RIGHT" row.prop(self.props, "should_show_visualisation_ui", text="Animation Settings", icon="SETTINGS") @@ -598,7 +602,7 @@ class BIM_PT_animation_Color_Scheme(Panel): if not AnimationColorSchemeData.is_loaded: AnimationColorSchemeData.load() - self.animation_props = context.scene.BIMAnimationProperties + self.animation_props = tool.Sequence.get_animation_props() row = self.layout.row(align=True) row.alignment = "RIGHT" row.operator("bim.load_default_animation_color_scheme", text="Load default", icon="SEQUENCE_COLOR_04") @@ -645,7 +649,7 @@ class BIM_PT_task_icom(Panel): @classmethod def poll(cls, context): - props = context.scene.BIMWorkScheduleProperties + props = tool.Sequence.get_work_schedule_props() if not props.active_work_schedule_id: return False tprops = tool.Sequence.get_task_tree_props() @@ -658,7 +662,7 @@ class BIM_PT_task_icom(Panel): if not TaskICOMData.is_loaded: TaskICOMData.load() - self.props = context.scene.BIMWorkScheduleProperties + self.props = tool.Sequence.get_work_schedule_props() self.tprops = tool.Sequence.get_task_tree_props() task = self.tprops.tasks[self.props.active_task_index] @@ -751,8 +755,17 @@ class BIM_PT_task_icom(Panel): class BIM_UL_task_columns(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = context.scene.BIMWorkScheduleProperties + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: "BIMWorkScheduleProperties", + item: "Attribute", + icon, + active_data, + active_propname, + ): + props = tool.Sequence.get_work_schedule_props() if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") @@ -820,7 +833,7 @@ class BIM_UL_product_output_tasks(UIList): class BIM_UL_tasks(UIList): @classmethod def draw_header(cls, layout: bpy.types.UILayout): - props = bpy.context.scene.BIMWorkScheduleProperties + props = tool.Sequence.get_work_schedule_props() row = layout.row(align=True) split1 = row.split(factor=0.1) @@ -829,9 +842,18 @@ class BIM_UL_tasks(UIList): split2.label(text="Name") cls.draw_custom_columns(props, split2, header=True) - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: "BIMTaskTreeProperties", + item: "Task", + icon, + active_data, + active_propname, + ): if item: - self.props = context.scene.BIMWorkScheduleProperties + self.props = tool.Sequence.get_work_schedule_props() task = SequenceData.data["tasks"][item.ifc_definition_id] row = layout.row(align=True) @@ -1140,7 +1162,7 @@ class BIM_PT_4D_Tools(Panel): bl_parent_id = "BIM_PT_tab_sequence" def draw(self, context): - self.props = context.scene.BIMWorkScheduleProperties + self.props = tool.Sequence.get_work_schedule_props() row = self.layout.row() row.operator("bim.load_product_related_tasks", text="Load Tasks", icon="FILE_REFRESH") row.prop(self.props, "filter_by_active_schedule", text="Filter by Active Schedule") diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index d5269a405f..6136c0e6da 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -214,11 +214,11 @@ class Blender(bonsai.core.tool.Blender): return omprops.active_material_set_item_id elif obj_type == "Task": tprops = tool.Sequence.get_task_tree_props() - return tprops.tasks[context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id + wsprops = tool.Sequence.get_work_schedule_props() + return tprops.tasks[wsprops.active_task_index].ifc_definition_id elif obj_type == "Cost": - return context.scene.BIMCostProperties.cost_items[ - context.scene.BIMCostProperties.active_cost_item_index - ].ifc_definition_id + 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 @@ -227,7 +227,8 @@ class Blender(bonsai.core.tool.Blender): props = tool.Profile.get_profile_props() return props.profiles[props.active_profile_index].ifc_definition_id elif obj_type == "WorkSchedule": - return context.scene.BIMWorkScheduleProperties.active_work_schedule_id + wsprops = tool.Sequence.get_work_schedule_props() + return wsprops.active_work_schedule_id elif obj_type == "Group": prop = context.scene.BIMGroupProperties return prop.groups[prop.active_group_index].ifc_definition_id diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py index 251c03c61b..3775da554c 100644 --- a/src/bonsai/bonsai/tool/cost.py +++ b/src/bonsai/bonsai/tool/cost.py @@ -1,4 +1,22 @@ -import os +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult , 2022 Yassine Oualid +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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.core.tool import bonsai.tool as tool @@ -10,27 +28,35 @@ import ifcopenshell.util.unit import bonsai.bim.helper import json from pathlib import Path -from typing import Optional, Any, Generator, Union, Literal +from typing import Optional, Any, Generator, Union, Literal, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity class Cost(bonsai.core.tool.Cost): RELATED_OBJECT_TYPE = Literal["PRODUCT", "PROCESS", "RESOURCE"] + @classmethod + def get_cost_props(cls) -> "BIMCostProperties": + return bpy.context.scene.BIMCostProperties + @classmethod def get_cost_schedule_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() return bonsai.bim.helper.export_attributes(props.cost_schedule_attributes) @classmethod def disable_editing_cost_schedule(cls) -> None: cls.store_active_schedule_columns() - bpy.context.scene.BIMCostProperties.active_cost_schedule_id = 0 + props = cls.get_cost_props() + props.active_cost_schedule_id = 0 cls.disable_editing_cost_item() @classmethod def load_active_schedule_columns(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() active_columns = props.columns storage = props.columns_storage active_cost_schedule_id = cls.get_active_cost_schedule().id() @@ -53,7 +79,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def store_active_schedule_columns(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() active_columns = props.columns storage = props.columns_storage active_cost_schedule_id = cls.get_active_cost_schedule().id() @@ -67,7 +93,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def remove_stored_schedule_columns(cls, cost_schedule: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() storage = props.columns_storage active_cost_schedule_id = cost_schedule.id() @@ -79,8 +105,9 @@ class Cost(bonsai.core.tool.Cost): @classmethod def enable_editing_cost_schedule_attributes(cls, cost_schedule: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMCostProperties.active_cost_schedule_id = cost_schedule.id() - bpy.context.scene.BIMCostProperties.is_editing = "COST_SCHEDULE_ATTRIBUTES" + props = cls.get_cost_props() + props.active_cost_schedule_id = cost_schedule.id() + props.is_editing = "COST_SCHEDULE_ATTRIBUTES" @classmethod def load_cost_schedule_attributes(cls, cost_schedule: ifcopenshell.entity_instance) -> None: @@ -89,13 +116,13 @@ class Cost(bonsai.core.tool.Cost): prop.string_value = "" if prop.is_null else ifcopenshell.util.date.ifc2datetime(data[name]).isoformat() return True - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.cost_schedule_attributes.clear() bonsai.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import) @classmethod def enable_editing_cost_items(cls, cost_schedule: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_schedule_id = cost_schedule.id() props.is_editing = "COST_ITEMS" @@ -123,7 +150,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def load_cost_schedule_tree(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.is_cost_update_enabled = False cost_schedule = tool.Ifc.get().by_id(props.active_cost_schedule_id) props.cost_items.clear() @@ -137,7 +164,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def expand_cost_item(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) if cost_item.id() in cls.contracted_cost_items: @@ -146,7 +173,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def expand_cost_items(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() cls.contracted_cost_items = json.loads(props.contracted_cost_items) for cost_item in props.cost_items: if cost_item.ifc_definition_id in cls.contracted_cost_items: @@ -155,7 +182,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def contract_cost_item(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) cls.contracted_cost_items.append(cost_item.id()) @@ -163,7 +190,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def contract_cost_items(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) for cost_item in props.cost_items: @@ -173,7 +200,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def clean_up_cost_item_tree(cls, cost_item_id: int) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) if props.active_cost_item_id == cost_item_id: @@ -185,35 +212,37 @@ class Cost(bonsai.core.tool.Cost): @classmethod def enable_editing_cost_item_attributes(cls, cost_item: ifcopenshell.entity_instance): - bpy.context.scene.BIMCostProperties.active_cost_item_id = cost_item.id() - bpy.context.scene.BIMCostProperties.cost_item_editing_type = "ATTRIBUTES" + props = cls.get_cost_props() + props.active_cost_item_id = cost_item.id() + props.cost_item_editing_type = "ATTRIBUTES" @classmethod def load_cost_item_attributes(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.cost_item_attributes.clear() bonsai.bim.helper.import_attributes2(cost_item, props.cost_item_attributes) @classmethod def disable_editing_cost_item(cls) -> None: - bpy.context.scene.BIMCostProperties.active_cost_item_id = 0 - bpy.context.scene.BIMCostProperties.change_cost_item_parent = False + props = cls.get_cost_props() + props.active_cost_item_id = 0 + props.change_cost_item_parent = False @classmethod def get_cost_item_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() return bonsai.bim.helper.export_attributes(props.cost_item_attributes) @classmethod def get_active_cost_item(cls) -> Union[ifcopenshell.entity_instance, None]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not props.active_cost_item_id: return None - return tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_item_id) + return tool.Ifc.get().by_id(props.active_cost_item_id) @classmethod def get_highlighted_cost_item(cls) -> Union[ifcopenshell.entity_instance, None]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if not props.active_cost_schedule_id: return if props.active_cost_item_index < len(props.cost_items): @@ -226,7 +255,7 @@ class Cost(bonsai.core.tool.Cost): cost_item = cls.get_highlighted_cost_item() if not cost_item: return - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.cost_item_type_products.clear() # TODO implement process and resource types # props.cost_item_processes.clear() @@ -248,7 +277,9 @@ class Cost(bonsai.core.tool.Cost): cls, cost_item: ifcopenshell.entity_instance, related_object_type: RELATED_OBJECT_TYPE ) -> None: def create_list_items( - collection: bpy.types.CollectionProperty, cost_item: ifcopenshell.entity_instance, is_deep: bool + collection: bpy.types.bpy_prop_collection_idprop[CostItemQuantity], + cost_item: ifcopenshell.entity_instance, + is_deep: bool, ) -> None: products = cls.get_cost_item_assignments(cost_item, filter_by_type=related_object_type, is_deep=False) for product in products: @@ -262,18 +293,18 @@ class Cost(bonsai.core.tool.Cost): for cost_item in ifcopenshell.util.cost.get_nested_cost_items(cost_item, is_deep): create_list_items(collection, cost_item, is_deep=False) - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if related_object_type == "PRODUCT": props.cost_item_products.clear() - is_deep = bpy.context.scene.BIMCostProperties.show_nested_elements + is_deep = props.show_nested_elements create_list_items(props.cost_item_products, cost_item, is_deep) elif related_object_type == "PROCESS": props.cost_item_processes.clear() - is_deep = bpy.context.scene.BIMCostProperties.show_nested_tasks + is_deep = props.show_nested_tasks create_list_items(props.cost_item_processes, cost_item, is_deep) elif related_object_type == "RESOURCE": props.cost_item_resources.clear() - is_deep = bpy.context.scene.BIMCostProperties.show_nested_resources + is_deep = props.show_nested_resources create_list_items(props.cost_item_resources, cost_item, is_deep) @classmethod @@ -321,33 +352,35 @@ class Cost(bonsai.core.tool.Cost): @classmethod def enable_editing_cost_item_quantities(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_item_id = cost_item.id() props.cost_item_editing_type = "QUANTITIES" @classmethod def enable_editing_cost_item_quantity(cls, physical_quantity: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMCostProperties.active_cost_item_quantity_id = physical_quantity.id() + props = cls.get_cost_props() + props.active_cost_item_quantity_id = physical_quantity.id() @classmethod def load_cost_item_quantity_attributes(cls, physical_quantity: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.quantity_attributes.clear() bonsai.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes) @classmethod def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_item_id = cost_item.id() props.cost_item_editing_type = "VALUES" @classmethod def disable_editing_cost_item_quantity(cls) -> None: - bpy.context.scene.BIMCostProperties.active_cost_item_quantity_id = 0 + props = cls.get_cost_props() + props.active_cost_item_quantity_id = 0 @classmethod def get_cost_item_quantity_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() return bonsai.bim.helper.export_attributes(props.quantity_attributes) @classmethod @@ -356,7 +389,8 @@ class Cost(bonsai.core.tool.Cost): ) -> dict[str, Any]: if cost_type == "FIXED": category = None - attributes = {"AppliedValue": bpy.context.scene.BIMCostProperties.fixed_cost_value} + props = cls.get_cost_props() + attributes = {"AppliedValue": props.fixed_cost_value} elif cost_type == "SUM": category = "*" attributes = {"Category": category} @@ -404,7 +438,7 @@ class Cost(bonsai.core.tool.Cost): break return True - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.cost_value_attributes.clear() is_rates = cls.is_active_schedule_of_rates() callback = lambda name, prop, data: import_attributes( @@ -420,38 +454,37 @@ class Cost(bonsai.core.tool.Cost): @classmethod def is_active_schedule_of_rates(cls) -> bool: - return ( - tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id).PredefinedType - == "SCHEDULEOFRATES" - ) + props = cls.get_cost_props() + return tool.Ifc.get().by_id(props.active_cost_schedule_id).PredefinedType == "SCHEDULEOFRATES" @classmethod def enable_editing_cost_item_value(cls, cost_value: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_value_id = cost_value.id() props.cost_value_editing_type = "ATTRIBUTES" @classmethod def disable_editing_cost_item_value(cls) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_value_id = 0 props.cost_value_editing_type = "" @classmethod def load_cost_item_value_formula_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.cost_value_attributes.clear() - bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value) + props.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value) @classmethod def enable_editing_cost_item_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.active_cost_value_id = cost_value.id() props.cost_value_editing_type = "FORMULA" @classmethod def get_cost_item_value_formula(cls) -> str: - return bpy.context.scene.BIMCostProperties.cost_value_formula + props = cls.get_cost_props() + return props.cost_value_formula @classmethod def get_cost_value_attributes(cls) -> dict[str, Any]: @@ -468,15 +501,14 @@ class Cost(bonsai.core.tool.Cost): if prop.name == "UnitBasisUnit": return True - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() callback = lambda attributes, prop: export_attributes(attributes, prop) return bonsai.bim.helper.export_attributes(props.cost_value_attributes, callback) @classmethod def get_cost_value_unit_component(cls) -> ifcopenshell.entity_instance: - return tool.Ifc.get().by_id( - int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) - ) + props = cls.get_cost_props() + return tool.Ifc.get().by_id(int(props.cost_value_attributes["UnitBasisUnit"].enum_value)) @classmethod def get_cost_item_assignments( @@ -491,7 +523,8 @@ class Cost(bonsai.core.tool.Cost): @classmethod def show_nested_cost_item_elements(cls) -> bool: - return bpy.context.scene.BIMCostProperties.show_nested_elements + props = cls.get_cost_props() + return props.show_nested_elements @classmethod def get_cost_item_products( @@ -543,18 +576,18 @@ class Cost(bonsai.core.tool.Cost): @classmethod def add_cost_column(cls, name: str) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() new = props.columns.add() new.name = name @classmethod def remove_cost_column(cls, name: str) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.columns.remove(props.columns.find(name)) @classmethod def get_active_schedule_of_rates(cls) -> Union[ifcopenshell.entity_instance, None]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() schedule_id = tool.Blender.get_enum_safe(props, "schedule_of_rates") if schedule_id is None: return @@ -562,7 +595,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def expand_cost_item_rate(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) contracted_cost_item_rates.remove(cost_item) props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) @@ -570,7 +603,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def contract_cost_item_rate(cls, cost_item: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) contracted_cost_item_rates.append(cost_item) props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) @@ -605,7 +638,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def load_schedule_of_rates_tree(cls, schedule_of_rates: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.is_cost_update_enabled = False props.cost_item_rates.clear() props.columns.clear() @@ -689,13 +722,15 @@ class Cost(bonsai.core.tool.Cost): @classmethod def is_cost_schedule_active(cls, cost_schedule: ifcopenshell.entity_instance) -> bool: - return True if cost_schedule.id() == bpy.context.scene.BIMCostProperties.active_cost_schedule_id else False + props = cls.get_cost_props() + return True if cost_schedule.id() == props.active_cost_schedule_id else False @classmethod def get_active_cost_schedule(cls) -> Union[ifcopenshell.entity_instance, None]: - if not bpy.context.scene.BIMCostProperties.active_cost_schedule_id: + props = cls.get_cost_props() + if not props.active_cost_schedule_id: return None - return tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id) + return tool.Ifc.get().by_id(props.active_cost_schedule_id) @classmethod def highlight_cost_item(cls, cost_item: ifcopenshell.entity_instance) -> None: @@ -707,13 +742,11 @@ class Cost(bonsai.core.tool.Cost): expand_ancestors(parent_cost) cls.load_cost_schedule_tree() - cost_props = bpy.context.scene.BIMCostProperties + cost_props = cls.get_cost_props() if not cost_item.id() in [item.ifc_definition_id for item in cost_props.cost_items]: expand_ancestors(cost_item) - cost_item_index = [item.ifc_definition_id for item in bpy.context.scene.BIMCostProperties.cost_items].index( - cost_item.id() - ) or 0 - bpy.context.scene.BIMCostProperties.active_cost_item_index = cost_item_index + cost_item_index = [item.ifc_definition_id for item in cost_props.cost_items].index(cost_item.id()) or 0 + cost_props.active_cost_item_index = cost_item_index @classmethod def get_cost_items_for_product(cls, product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: @@ -732,7 +765,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def load_product_cost_items(cls, product: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() props.is_cost_update_enabled = False props.product_cost_items.clear() cost_items = ifcopenshell.util.cost.get_cost_items_for_product(product) @@ -766,7 +799,7 @@ class Cost(bonsai.core.tool.Cost): def toggle_cost_item_parent_change(cls, cost_item: Optional[ifcopenshell.entity_instance] = None) -> None: if not cost_item: return - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() if props.change_cost_item_parent: props.active_cost_item_id = cost_item.id() props.cost_item_editing_type = "PARENT" @@ -781,8 +814,9 @@ class Cost(bonsai.core.tool.Cost): @classmethod def disable_editing_cost_item_parent(cls) -> None: - bpy.context.scene.BIMCostProperties.active_cost_item_id = 0 - bpy.context.scene.BIMCostProperties.change_cost_item_parent = False + props = cls.get_cost_props() + props.active_cost_item_id = 0 + props.change_cost_item_parent = False @classmethod def load_cost_item_quantities(cls, cost_item: Optional[ifcopenshell.entity_instance] = None) -> None: @@ -822,7 +856,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def get_currency_attributes(cls) -> dict[str, str]: - props = bpy.context.scene.BIMCostProperties + props = cls.get_cost_props() currency = props.currency if currency == "CUSTOM": currency = props.custom_currency diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 046906387e..4b3e1064ee 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -42,17 +42,25 @@ 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 + from bonsai.bim.module.sequence.prop import BIMTaskTreeProperties, BIMWorkScheduleProperties, BIMAnimationProperties class Sequence(bonsai.core.tool.Sequence): RELATED_OBJECT_TYPE = Literal["RESOURCE", "PRODUCT", "CONTROL"] + @classmethod + def get_animation_props(cls) -> BIMAnimationProperties: + return bpy.context.scene.BIMAnimationProperties + @classmethod def get_task_tree_props(cls) -> BIMTaskTreeProperties: return bpy.context.scene.BIMTaskTreeProperties + @classmethod + def get_work_schedule_props(cls) -> BIMWorkScheduleProperties: + return bpy.context.scene.BIMWorkScheduleProperties + @classmethod def get_work_plan_attributes(cls) -> dict[str, Any]: import bonsai.bim.module.sequence.helper as helper @@ -119,7 +127,7 @@ class Sequence(bonsai.core.tool.Sequence): attributes[prop.name] = helper.parse_duration(prop.string_value) return True - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() return bonsai.bim.helper.export_attributes(props.work_schedule_attributes, callback) @classmethod @@ -129,23 +137,25 @@ class Sequence(bonsai.core.tool.Sequence): prop.string_value = "" if prop.is_null else data[name] return True - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.work_schedule_attributes.clear() bonsai.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback) @classmethod def enable_editing_work_schedule(cls, work_schedule: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = work_schedule.id() - bpy.context.scene.BIMWorkScheduleProperties.editing_type = "WORK_SCHEDULE" + props = cls.get_work_schedule_props() + props.active_work_schedule_id = work_schedule.id() + props.editing_type = "WORK_SCHEDULE" @classmethod def disable_editing_work_schedule(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + props = cls.get_work_schedule_props() + props.active_work_schedule_id = 0 @classmethod def enable_editing_work_schedule_tasks(cls, work_schedule: Union[ifcopenshell.entity_instance, None]) -> None: if work_schedule: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_work_schedule_id = work_schedule.id() props.editing_type = "TASKS" @@ -153,7 +163,7 @@ class Sequence(bonsai.core.tool.Sequence): def load_task_tree(cls, work_schedule: ifcopenshell.entity_instance) -> None: props = cls.get_task_tree_props() props.tasks.clear() - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() cls.contracted_tasks = json.loads(props.contracted_tasks) related_objects_ids = cls.get_sorted_tasks_ids(ifcopenshell.util.sequence.get_root_tasks(work_schedule)) @@ -162,13 +172,15 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_sorted_tasks_ids(cls, tasks: list[ifcopenshell.entity_instance]) -> list[int]: + props = cls.get_work_schedule_props() + def get_sort_key(task): # Sorting only applies to actual tasks, not the WBS # for rel in task.IsNestedBy: # for object in rel.RelatedObjects: # if object.is_a("IfcTask"): # return "0000000000" + (task.Identification or "") - column_type, name = bpy.context.scene.BIMWorkScheduleProperties.sort_column.split(".") + column_type, name = props.sort_column.split(".") if column_type == "IfcTask": return task.get_info(task)[name] or "" elif column_type == "IfcTaskTime" and task.TaskTime: @@ -179,12 +191,12 @@ class Sequence(bonsai.core.tool.Sequence): s = sort_keys[i] return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] - if bpy.context.scene.BIMWorkScheduleProperties.sort_column: + if props.sort_column: sort_keys = {task.id(): get_sort_key(task) for task in tasks} related_object_ids = sorted(sort_keys, key=natural_sort_key) else: related_object_ids = [task.id() for task in tasks] - if bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed: + if props.is_sort_reversed: related_object_ids.reverse() return related_object_ids @@ -205,7 +217,7 @@ class Sequence(bonsai.core.tool.Sequence): # TODO: task argument is never used? @classmethod def load_task_properties(cls, task: Optional[ifcopenshell.entity_instance] = None) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() task_props = cls.get_task_tree_props() tasks_with_visual_bar = cls.get_task_bar_list() props.is_task_update_enabled = False @@ -270,24 +282,26 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_active_work_schedule(cls) -> Union[ifcopenshell.entity_instance, None]: - if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + props = cls.get_work_schedule_props() + if not props.active_work_schedule_id: return None - return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id) + return tool.Ifc.get().by_id(props.active_work_schedule_id) @classmethod def expand_task(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() contracted_tasks = json.loads(props.contracted_tasks) contracted_tasks.remove(task.id()) props.contracted_tasks = json.dumps(contracted_tasks) @classmethod def expand_all_tasks(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps([]) + props = cls.get_work_schedule_props() + props.contracted_tasks = json.dumps([]) @classmethod def contract_all_tasks(cls) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() tprops = cls.get_task_tree_props() contracted_tasks = json.loads(props.contracted_tasks) for task_item in tprops.tasks: @@ -297,23 +311,24 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def contract_task(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() contracted_tasks = json.loads(props.contracted_tasks) contracted_tasks.append(task.id()) props.contracted_tasks = json.dumps(contracted_tasks) @classmethod def disable_work_schedule(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + props = cls.get_work_schedule_props() + props.active_work_schedule_id = 0 @classmethod def disable_selecting_deleted_task(cls) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() if props.active_task_id not in [ 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 + props.active_task_id = 0 + props.active_task_time_id = 0 @classmethod def get_checked_tasks(cls) -> list[ifcopenshell.entity_instance]: @@ -323,11 +338,13 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_task_attribute_value(cls, attribute_name: str) -> Any: - return bpy.context.scene.BIMWorkScheduleProperties.task_attributes.get(attribute_name).get_value() + props = cls.get_work_schedule_props() + return props.task_attributes.get(attribute_name).get_value() @classmethod def get_active_task(cls) -> ifcopenshell.entity_instance: - return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_task_id) + props = cls.get_work_schedule_props() + return tool.Ifc.get().by_id(props.active_task_id) @classmethod def get_active_work_time(cls) -> ifcopenshell.entity_instance: @@ -339,31 +356,34 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_task_attributes(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.task_attributes.clear() bonsai.bim.helper.import_attributes2(task, props.task_attributes) @classmethod def enable_editing_task_attributes(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_task_id = task.id() props.editing_task_type = "ATTRIBUTES" @classmethod def get_task_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.task_attributes) + props = cls.get_work_schedule_props() + return bonsai.bim.helper.export_attributes(props.task_attributes) @classmethod def load_task_time_attributes(cls, task_time: ifcopenshell.entity_instance) -> None: import bonsai.bim.module.sequence.helper as helper + props = cls.get_work_schedule_props() + def callback( name: str, prop: Union[bonsai.bim.prop.Attribute, None], data: dict[str, Any] ) -> Union[bool, None]: if prop and prop.data_type == "string": # TODO: Check actual attribute type instead of providing attribute names. if name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"): - duration_props = bpy.context.scene.BIMWorkScheduleProperties.durations_attributes.add() + duration_props = props.durations_attributes.add() duration_props.name = name if prop.is_null: for key in duration_props.keys(): @@ -378,28 +398,30 @@ class Sequence(bonsai.core.tool.Sequence): prop.string_value = "" if prop.is_null else data[name].isoformat() return True - props = bpy.context.scene.BIMWorkScheduleProperties props.task_time_attributes.clear() props.durations_attributes.clear() bonsai.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback) @classmethod def enable_editing_task_time(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_task_id = task.id() props.active_task_time_id = task.TaskTime.id() props.editing_task_type = "TASKTIME" @classmethod def disable_editing_task(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 - bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 - bpy.context.scene.BIMWorkScheduleProperties.editing_task_type = "" + props = cls.get_work_schedule_props() + props.active_task_id = 0 + props.active_task_time_id = 0 + props.editing_task_type = "" @classmethod def get_task_time_attributes(cls) -> dict[str, Any]: import bonsai.bim.module.sequence.helper as helper + props = cls.get_work_schedule_props() + def callback(attributes, prop): if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": if prop.is_null: @@ -408,7 +430,6 @@ class Sequence(bonsai.core.tool.Sequence): attributes[prop.name] = helper.parse_datetime(prop.string_value) return True elif prop.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]: - props = bpy.context.scene.BIMWorkScheduleProperties if prop.is_null: attributes[prop.name] = None for value in props.durations_attributes.values(): @@ -424,13 +445,12 @@ class Sequence(bonsai.core.tool.Sequence): value = 0 return True - props = bpy.context.scene.BIMWorkScheduleProperties return bonsai.bim.helper.export_attributes(props.task_time_attributes, callback) @classmethod def load_task_resources(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties - rprops = bpy.context.scene.BIMResourceProperties + props = cls.get_work_schedule_props() + rprops = cls.get_resource_props() props.task_resources.clear() rprops.is_resource_update_enabled = False for resource in cls.get_task_resources(task) or []: @@ -442,12 +462,14 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_task_inputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: - is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_inputs + props = cls.get_work_schedule_props() + is_deep = props.show_nested_inputs return ifcopenshell.util.sequence.get_task_inputs(task, is_deep) @classmethod def get_task_outputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: - is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs + props = cls.get_work_schedule_props() + is_deep = props.show_nested_outputs return ifcopenshell.util.sequence.get_task_outputs(task, is_deep) @classmethod @@ -468,12 +490,13 @@ class Sequence(bonsai.core.tool.Sequence): ) -> Union[list[ifcopenshell.entity_instance], None]: if not task: return - is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources + props = cls.get_work_schedule_props() + is_deep = props.show_nested_resources return ifcopenshell.util.sequence.get_task_resources(task, is_deep) @classmethod def load_task_inputs(cls, inputs: list[ifcopenshell.entity_instance]) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.task_inputs.clear() for input in inputs: new = props.task_inputs.add() @@ -482,7 +505,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_task_outputs(cls, outputs: list[ifcopenshell.entity_instance]) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.task_outputs.clear() if outputs: for output in outputs: @@ -493,10 +516,9 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_highlighted_task(cls) -> Union[ifcopenshell.entity_instance, None]: 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 - ) + props = cls.get_work_schedule_props() + if len(tasks) and len(tasks) > props.active_task_index: + return tool.Ifc.get().by_id(tasks[props.active_task_index].ifc_definition_id) @classmethod def get_direct_nested_tasks(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: @@ -648,37 +670,40 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def enable_editing_task_calendar(cls, task: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_task_id = task.id() props.editing_task_type = "CALENDAR" @classmethod def enable_editing_task_sequence(cls) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.editing_task_type = "SEQUENCE" @classmethod def disable_editing_task_time(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 - bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + props = cls.get_work_schedule_props() + props.active_task_id = 0 + props.active_task_time_id = 0 @classmethod def load_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.sequence_attributes.clear() bonsai.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes) @classmethod def enable_editing_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_sequence_id = rel_sequence.id() props.editing_sequence_type = "ATTRIBUTES" @classmethod def load_lag_time_attributes(cls, lag_time: ifcopenshell.entity_instance) -> None: + props = cls.get_work_schedule_props() + def callback(name, prop, data): if name == "LagValue": - prop = bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes.add() + prop = props.lag_time_attributes.add() prop.name = name prop.is_null = data[name] is None prop.is_optional = False @@ -688,27 +713,29 @@ class Sequence(bonsai.core.tool.Sequence): ) return True - props = bpy.context.scene.BIMWorkScheduleProperties props.lag_time_attributes.clear() bonsai.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback) @classmethod def enable_editing_sequence_lag_time(cls, rel_sequence: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.active_sequence_id = rel_sequence.id() props.editing_sequence_type = "LAG_TIME" @classmethod def get_rel_sequence_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.sequence_attributes) + props = cls.get_work_schedule_props() + return bonsai.bim.helper.export_attributes(props.sequence_attributes) @classmethod def disable_editing_rel_sequence(cls) -> None: - bpy.context.scene.BIMWorkScheduleProperties.active_sequence_id = 0 + props = cls.get_work_schedule_props() + props.active_sequence_id = 0 @classmethod def get_lag_time_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes) + props = cls.get_work_schedule_props() + return bonsai.bim.helper.export_attributes(props.lag_time_attributes) @classmethod def select_products(cls, products: Iterable[ifcopenshell.entity_instance]) -> None: @@ -719,14 +746,14 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def add_task_column(cls, column_type: str, name: str, data_type: str) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() new = props.columns.add() new.name = f"{column_type}.{name}" new.data_type = data_type @classmethod def setup_default_task_columns(cls) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.columns.clear() default_columns = ["ScheduleStart", "ScheduleFinish", "ScheduleDuration"] for item in default_columns: @@ -736,14 +763,14 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def remove_task_column(cls, name: str) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.columns.remove(props.columns.find(name)) if props.sort_column == name: props.sort_column = "" @classmethod def set_task_sort_column(cls, column: str) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.sort_column = column @classmethod @@ -772,12 +799,13 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def is_work_schedule_active(cls, work_schedule): - return ( - True if work_schedule.id() == bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id else False - ) + props = cls.get_work_schedule_props() + return True if work_schedule.id() == props.active_work_schedule_id else False @classmethod def go_to_task(cls, task): + props = cls.get_work_schedule_props() + def get_ancestor_ids(task): ids = [] for rel in task.Nests or []: @@ -785,11 +813,11 @@ class Sequence(bonsai.core.tool.Sequence): ids.extend(get_ancestor_ids(rel.RelatingObject)) return ids - contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks) + contracted_tasks = json.loads(props.contracted_tasks) for ancestor_id in get_ancestor_ids(task): if ancestor_id in contracted_tasks: contracted_tasks.remove(ancestor_id) - bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks) + props.contracted_tasks = json.dumps(contracted_tasks) work_schedule = cls.get_active_work_schedule() cls.load_task_tree(work_schedule) @@ -797,7 +825,7 @@ class Sequence(bonsai.core.tool.Sequence): 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 + props.active_task_index = expanded_tasks.index(task.id()) or 0 # TODO: proper typing @classmethod @@ -808,7 +836,7 @@ class Sequence(bonsai.core.tool.Sequence): def update_visualisation_date(cls, start_date, finish_date): if not (start_date and finish_date): return - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.visualisation_start = ifcopenshell.util.date.canonicalise_time(start_date) props.visualisation_finish = ifcopenshell.util.date.canonicalise_time(finish_date) @@ -849,7 +877,7 @@ class Sequence(bonsai.core.tool.Sequence): } def create_task_bar_data(tasks, vertical_increment, collection): - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() settings = { "viz_start": ( parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True) @@ -874,7 +902,8 @@ class Sequence(bonsai.core.tool.Sequence): position_shift = task_data["start_frame"] * size_to_duration_ratio bar_size = (task_data["finish_frame"] - task_data["start_frame"]) * size_to_duration_ratio - color_progress = bpy.context.scene.BIMAnimationProperties.color_progress + anim_props = cls.get_animation_props() + color_progress = anim_props.color_progress bar = add_bar( material=material_progress, vertical_increment=vertical_increment, @@ -887,7 +916,7 @@ class Sequence(bonsai.core.tool.Sequence): name=task_data["name"] + "/Progress Bar", ) - color_full = bpy.context.scene.BIMAnimationProperties.color_full + color_full = anim_props.color_full bar2 = add_bar( material=material_full, vertical_increment=vertical_increment, @@ -1082,7 +1111,7 @@ class Sequence(bonsai.core.tool.Sequence): "Color": (0.2, 0.2, 0.2), }, } - props = bpy.context.scene.BIMAnimationProperties + props = cls.get_animation_props() props.task_output_colors.clear() props.task_input_colors.clear() for group, data in groups.items(): @@ -1102,14 +1131,14 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_start_date(cls) -> Union[datetime, None]: - start = parser.parse(bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, dayfirst=True, fuzzy=True) + props = cls.get_work_schedule_props() + start = parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True) return start or None @classmethod def get_finish_date(cls) -> Union[datetime, None]: - finish = parser.parse( - bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish, dayfirst=True, fuzzy=True - ) + props = cls.get_work_schedule_props() + finish = parser.parse(props.visualisation_finish, dayfirst=True, fuzzy=True) return finish or None @classmethod @@ -1241,7 +1270,7 @@ class Sequence(bonsai.core.tool.Sequence): def calculate_using_frames(start, finish, animation_frames, real_duration): return ((finish - start) / real_duration) * animation_frames - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() if not (props.visualisation_start and props.visualisation_finish): return @@ -1346,7 +1375,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def animate_input(cls, obj, start_frame, product_frame, animation_type): - props = bpy.context.scene.BIMAnimationProperties + props = cls.get_animation_props() color = props.task_input_colors[product_frame["type"]].color if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]: cls.animate_destruction(obj, start_frame, product_frame, color, animation_type) @@ -1355,7 +1384,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def animate_output(cls, obj, start_frame, product_frame, animation_type): - props = bpy.context.scene.BIMAnimationProperties + props = cls.get_animation_props() color = props.task_output_colors[product_frame["type"]].color if product_frame["type"] in ["CONSTRUCTION", "INSTALLATION", "NOTDEFINED"]: cls.animate_creation(obj, start_frame, product_frame, color) @@ -1466,8 +1495,9 @@ class Sequence(bonsai.core.tool.Sequence): obj.data.BIMDateTextProperties.start_frame = settings["start_frame"] obj.data.BIMDateTextProperties.total_frames = int(settings["total_frames"]) - obj.data.BIMDateTextProperties.start = bpy.context.scene.BIMWorkScheduleProperties.visualisation_start - obj.data.BIMDateTextProperties.finish = bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish + props = cls.get_work_schedule_props() + obj.data.BIMDateTextProperties.start = props.visualisation_start + obj.data.BIMDateTextProperties.finish = props.visualisation_finish append_handler(animate_text_handler) @classmethod @@ -1573,7 +1603,8 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def is_filter_by_active_schedule(cls) -> bool: - return bpy.context.scene.BIMWorkScheduleProperties.filter_by_active_schedule + props = cls.get_work_schedule_props() + return props.filter_by_active_schedule @classmethod def get_tasks_for_product( @@ -1585,7 +1616,7 @@ class Sequence(bonsai.core.tool.Sequence): def load_product_related_tasks( cls, task_inputs: list[ifcopenshell.entity_instance], task_ouputs: list[ifcopenshell.entity_instance] ) -> None: - props = bpy.context.scene.BIMWorkScheduleProperties + props = cls.get_work_schedule_props() props.product_input_tasks.clear() props.product_output_tasks.clear() for task in task_inputs or []: @@ -1617,18 +1648,21 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def is_sorting_enabled(cls): - return bpy.context.scene.BIMWorkScheduleProperties.sort_column + props = cls.get_work_schedule_props() + return props.sort_column @classmethod def is_sort_reversed(cls): - return bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed + props = cls.get_work_schedule_props() + return props.is_sort_reversed @classmethod def get_user_predefined_type(cls): - predefined_type = bpy.context.scene.BIMWorkScheduleProperties.work_schedule_predefined_types + props = cls.get_work_schedule_props() + predefined_type = props.work_schedule_predefined_types object_type = None if predefined_type == "USERDEFINED": - object_type = bpy.context.scene.BIMWorkScheduleProperties.object_type + object_type = props.object_type return predefined_type, object_type @classmethod @@ -1649,7 +1683,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def save_animation_color_scheme(cls, name: str) -> ifcopenshell.entity_instance: - props = bpy.context.scene.BIMAnimationProperties + props = cls.get_animation_props() colour_scheme = { "Inputs": {cs.name: cs.color[0:3] for cs in props.task_input_colors}, "Outputs": {cs.name: cs.color[0:3] for cs in props.task_output_colors}, @@ -1674,7 +1708,7 @@ class Sequence(bonsai.core.tool.Sequence): if data.get("type") == "BBIM_AnimationColorScheme": inputs_color_scheme = data.get("colourscheme").get("Inputs") outputs_color_scheme = data.get("colourscheme").get("Outputs") - props = bpy.context.scene.BIMAnimationProperties + props = cls.get_animation_props() props.task_input_colors.clear() props.task_output_colors.clear() for value, colour in inputs_color_scheme.items(): @@ -1711,26 +1745,30 @@ class Sequence(bonsai.core.tool.Sequence): return False @classmethod - def get_task_bar_list(cls): - return json.loads(bpy.context.scene.BIMWorkScheduleProperties.task_bars) + def get_task_bar_list(cls) -> list[int]: + props = cls.get_work_schedule_props() + return json.loads(props.task_bars) @classmethod - def add_task_bar(cls, task_id): + def add_task_bar(cls, task_id: int) -> None: task_bars = cls.get_task_bar_list() task_bars.append(task_id) - bpy.context.scene.BIMWorkScheduleProperties.task_bars = json.dumps(task_bars) + props = cls.get_work_schedule_props() + props.task_bars = json.dumps(task_bars) @classmethod - def remove_task_bar(cls, task_id): + def remove_task_bar(cls, task_id: int) -> None: task_bars = cls.get_task_bar_list() if task_id in task_bars: task_bars.remove(task_id) - bpy.context.scene.BIMWorkScheduleProperties.task_bars = json.dumps(task_bars) + props = cls.get_work_schedule_props() + props.task_bars = json.dumps(task_bars) @classmethod def get_animation_color_scheme(cls): - if len(bpy.context.scene.BIMAnimationProperties.saved_color_schemes) > 0: - return tool.Ifc.get().by_id(int(bpy.context.scene.BIMAnimationProperties.saved_color_schemes)) + props = cls.get_animation_props() + if len(props.saved_color_schemes) > 0: + return tool.Ifc.get().by_id(int(props.saved_color_schemes)) @classmethod def parse_isodate_datetime(cls, datetime_str: str, include_time: bool) -> datetime: diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 34e4b37b32..df4b54a0c0 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -456,7 +456,15 @@ class IfcCsv: self.import_pd(ifc_file, df, attributes, null, empty, bool_true, bool_false) def import_pd( - self, ifc_file, df, attributes=None, null="-", empty="", bool_true="YES", bool_false="NO", concat=", " + self, + ifc_file: ifcopenshell.file, + df: "pd.DataFrame", + attributes: Optional[list[Union[str, None]]] = None, + null: str = "-", + empty: str = "", + bool_true: str = "YES", + bool_false: str = "NO", + concat: str = ", ", ) -> None: headers = df.columns.tolist() diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py index fe10220a87..682dd4558d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py @@ -65,28 +65,22 @@ def assign_material( :param products: The list of IfcProducts to assign the material or material set to. - :type products: list[ifcopenshell.entity_instance] :param type: Choose from "IfcMaterial", "IfcMaterialConstituentSet", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialProfileSet", "IfcMaterialProfileSetUsage", or "IfcMaterialList". Note that "Set Usages" may only be assigned to occurrences, not types. Defaults to "IfcMaterial". - :type type: str :param material: The IfcMaterial or material set you are assigning here. If type is Usage then no need to provide `material`, it will be deduced from the element type automatically. If IfcMaterial is provided as material and type is not IfcMaterial, provided material will be ignored except for IfcMaterialList where it will be used as part of the list. - :type material: ifcopenshell.entity_instance, optional :return: IfcRelAssociatesMaterial entity or a list of IfcRelAssociatesMaterial entities (possible if `type` is Usage and `products` require different Usages) or `None` if `products` was empty list. - :rtype: Union[ - ifcopenshell.entity_instance, - list[ifcopenshell.entity_instance], None] Example: diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py b/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py index 51decaaa98..18477c6038 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py @@ -62,6 +62,8 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i set_items = material.MaterialConstituents or [] elif material.is_a("IfcMaterialList"): set_items = [] + else: + raise ValueError(f"Unknown material set type: {material.is_a()}") for set_item in set_items: file.remove(set_item) file.remove(material) diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py index f8386490c7..2ed3fd9e14 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py @@ -32,9 +32,7 @@ def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entit If the product does not have a material, nothing happens. :param products: The list IfcProducts that may or may not have a material - :type product: list[ifcopenshell.entity_instance] :return: None - :rtype: None Example: @@ -54,18 +52,16 @@ def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entit """ usecase = Usecase() usecase.file = file - usecase.settings = {"products": products} - return usecase.execute() + return usecase.execute(products) class Usecase: file: ifcopenshell.file - settings: dict[str, Any] - def execute(self): - self.products = set(self.settings["products"]) - if not self.products: + def execute(self, products: list[ifcopenshell.entity_instance]) -> None: + if not products: return + self.products = set(products) self.remove_material_usages_from_types() self.unassign_materials()