diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 49c7199d9f..77f96e7c9c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -68,6 +68,8 @@ classes = ( operator.ContractCostItemRate, operator.CalculateCostItemResourceValue, operator.ClearCostItemAssignments, + operator.HighlightProductCostItem, + operator.LoadProductCostItems, operator.SelectUnassignedProducts, operator.LoadCostItemElementQuantities, operator.LoadCostItemTaskQuantities, @@ -85,6 +87,8 @@ classes = ( ui.BIM_UL_cost_item_quantities, ui.BIM_UL_cost_item_types, ui.BIM_UL_cost_item_rates, + ui.BIM_UL_product_cost_items, + ui.BIM_PT_Costing_Tools, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index b25b653b13..579e89653b 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -456,9 +456,7 @@ class SelectCostItemProducts(bpy.types.Operator, tool.Ifc.Operator): cost_item: bpy.props.IntProperty() def _execute(self, context): - core.select_cost_item_products( - tool.Cost, tool.Spatial, cost_item=tool.Ifc.get().by_id(self.cost_item), include_nested=False - ) + core.select_cost_item_products(tool.Cost, tool.Spatial, cost_item=tool.Ifc.get().by_id(self.cost_item)) return {"FINISHED"} @@ -654,6 +652,38 @@ class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} +class LoadProductCostItems(bpy.types.Operator): + bl_idname = "bim.load_product_cost_items" + bl_label = "Get Product Cost Assignments" + bl_options = {"REGISTER", "UNDO"} + product: bpy.props.IntProperty() + + @classmethod + def poll(cls, context): + if not tool.Ifc.get(): + return False + if not context.active_object.BIMObjectProperties.ifc_definition_id: + return False + return True + + def execute(self, context): + core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(self.product)) + return {"FINISHED"} + + +class HighlightProductCostItem(bpy.types.Operator): + bl_idname = "bim.highlight_product_cost_item" + bl_label = "Highlight Product Cost Items" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + r = core.highlight_product_cost_item(tool.Spatial, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item)) + if isinstance(r, str): + self.report({"WARNING"}, r) + return {"FINISHED"} + + class SelectUnassignedProducts(bpy.types.Operator): bl_idname = "bim.select_unassigned_products" bl_label = "Select Unassigned Products" diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 680d30ea44..c230b3cf40 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -197,6 +197,8 @@ class BIMCostProperties(PropertyGroup): cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem) active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index") contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]") + product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem) + active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index") show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements) show_nested_tasks: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_tasks) show_nested_resources: BoolProperty( diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index f0a347efc9..ef26719702 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -64,6 +64,9 @@ class BIM_PT_cost_schedules(Panel): op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="") op.cost_schedule = cost_schedule["id"] row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY") + + row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned") + if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES": row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK") elif self.props.is_editing == "COST_ITEMS": @@ -377,7 +380,7 @@ class BIM_PT_cost_item_quantities(Panel): row2 = col.row() op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X") op.cost_item = cost_item.ifc_definition_id - op.related_object_type = "IfcElement" + op.related_object_type = "PRODUCT" if has_quantity_names: row2 = col.row() @@ -422,7 +425,7 @@ class BIM_PT_cost_item_quantities(Panel): row2 = col.row() op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X") op.cost_item = cost_item.ifc_definition_id - op.related_object_type = "IfcProcess" + op.related_object_type = "PROCESS" if has_quantity_names: row2 = col.row() @@ -470,7 +473,7 @@ class BIM_PT_cost_item_quantities(Panel): row2 = col.row() op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X") op.cost_item = cost_item.ifc_definition_id - op.related_object_type = "IfcResource" + op.related_object_type = "RESOURCE" if has_quantity_names: row2 = col.row() @@ -636,3 +639,41 @@ class BIM_UL_cost_item_quantities(UIList): op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X") op.cost_item = cost_item.ifc_definition_id op.related_object = item.ifc_definition_id + + +class BIM_UL_product_cost_items(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + props = context.scene.BIMCostProperties + oprops = context.active_object.BIMObjectProperties + cost_item = props.cost_items[props.active_cost_item_index] + + if item: + row = layout.row(align=True) + op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE") + op.cost_item = item.ifc_definition_id + row.split(factor=0.8) + row.label(text=item.name) + + +class BIM_PT_Costing_Tools(Panel): + bl_label = "5D Tools" + bl_idname = "BIM_PT_Costing_Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "4D/5D Toolkit" + + def draw(self, context): + self.props = context.scene.BIMCostProperties + row = self.layout.row() + row.operator( + "bim.load_product_cost_items", icon="FILE_REFRESH" + ).product = context.active_object.BIMObjectProperties.ifc_definition_id + row = self.layout.row() + row.template_list( + "BIM_UL_product_cost_items", + "", + self.props, + "product_cost_items", + self.props, + "active_product_cost_item_index", + ) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 8251d99589..3e9f3b7d3a 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -137,7 +137,7 @@ classes = ( ui.BIM_UL_task_resources, ui.BIM_UL_task_outputs, ui.BIM_UL_tasks, - ui.BIM_PT_Task_Tools, + ui.BIM_PT_4D_Tools, ui.BIM_PT_Task_Bar_Creator, ui.BIM_UL_animation_colors, ) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 95dab6e938..7356191324 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -790,12 +790,31 @@ class BIM_PT_work_calendars(Panel): draw_attributes(self.props.work_calendar_attributes, self.layout) -class BIM_PT_Task_Tools(Panel): +class BIM_PT_4D_Tools(Panel): + bl_label = "4D Tools" + bl_idname = "BIM_PT_4D_Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "4D/5D Toolkit" + + def draw(self, context): + row = self.layout.row() + row.operator( + "bim.highlight_product_related_task", text="Find Input-related Task ", icon="STYLUS_PRESSURE" + ).product_type = "Input" + row = self.layout.row() + row.operator( + "bim.highlight_product_related_task", text="Go to Output-related Task ", icon="STYLUS_PRESSURE" + ).product_type = "Output" + + +class BIM_PT_Task_Bar_Creator(Panel): bl_label = "Task Bar Creator" bl_idname = "BIM_PT_Task_Bar_Creator" bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = "Sequence Toolkit" + bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_4D_Tools" def draw(self, context): self.animation_props = context.scene.BIMAnimationProperties @@ -813,21 +832,3 @@ class BIM_PT_Task_Tools(Panel): row3 = col.row(align=True) row3.prop(self.animation_props, "color_full") - - -class BIM_PT_Task_Bar_Creator(Panel): - bl_label = "Task Tools" - bl_idname = "BIM_PT_Task_Tools" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Sequence Toolkit" - - def draw(self, context): - row = self.layout.row() - row.operator( - "bim.highlight_product_related_task", text="Find Input-related Task ", icon="STYLUS_PRESSURE" - ).product_type = "Input" - row = self.layout.row() - row.operator( - "bim.highlight_product_related_task", text="Go to Output-related Task ", icon="STYLUS_PRESSURE" - ).product_type = "Output" diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index 454376a549..9659bb30a0 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -247,3 +247,14 @@ def select_unassigned_products(ifc, cost, spatial): cost_schedule = cost.get_active_cost_schedule() selection = [product for product in products if not cost.has_cost_assignments(product, cost_schedule)] spatial.select_products(selection) + +def load_product_cost_items(cost, product): + cost.load_product_cost_items(product) + +def highlight_product_cost_item(spatial, cost, cost_item): + cost_schedule = cost.get_cost_schedule(cost_item) + is_cost_schedule_active = cost.is_cost_schedule_active(cost_schedule) + if is_cost_schedule_active: + cost.highlight_cost_item(cost_item) + else: + return "Cost schedule is not active" diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 989c73a714..0b33aaa0a2 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -543,12 +543,47 @@ class Cost(blenderbim.core.tool.Cost): name = f"{unit.Prefix} {name}" return f"{unit.UnitType} / {name}" + + @classmethod + def get_cost_schedule(cls, cost_item): + for rel in cost_item.HasAssignments or []: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"): + return rel.RelatingControl + for rel in cost_item.Nests or []: + return cls.get_cost_schedule(rel.RelatingObject) + + @classmethod + def is_cost_schedule_active(cls, cost_schedule): + return True if cost_schedule.id() == bpy.context.scene.BIMCostProperties.active_cost_schedule_id else False + @classmethod def get_active_cost_schedule(cls): if not bpy.context.scene.BIMCostProperties.active_cost_schedule_id: return None return tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id) + @classmethod + def highlight_cost_item(cls, cost_item): + def expand_ancestors(cost_item): + cls.expand_cost_item(cost_item) + for rel in cost_item.Nests or []: + parent_cost = rel.RelatingObject if rel.RelatingObject.is_a("IfcCostItem") else None + if parent_cost: + expand_ancestors(parent_cost) + cls.load_cost_schedule_tree() + + cost_props = bpy.context.scene.BIMCostProperties + 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 + + @classmethod + def get_cost_items_for_product(cls, product): + return ifcopenshell.util.cost.get_cost_items_for_product(product) + @classmethod def has_cost_assignments(cls, product, cost_schedule=None): cost_items = ifcopenshell.util.cost.get_cost_items_for_product(product) @@ -557,3 +592,23 @@ class Cost(blenderbim.core.tool.Cost): cost_item for cost_item in cost_items or [] if cls.get_cost_schedule(cost_item) == cost_schedule ] return bool(cost_items) + + @classmethod + def load_product_cost_items(cls, product): + props = bpy.context.scene.BIMCostProperties + props.is_cost_update_enabled = False + props.product_cost_items.clear() + cost_items = ifcopenshell.util.cost.get_cost_items_for_product(product) + if cost_items: + for cost_item in cost_items: + new = props.product_cost_items.add() + new.name = cost_item.Name or "Unnamed" + new.ifc_definition_id = cost_item.id() + + @classmethod + def is_root_cost_item(cls, cost_item): + if cost_item.HasAssignments: + for rel in cost_item.HasAssignments: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"): + return True +