From fc7459c07fa4f2b0183af4694732bb11559e7562 Mon Sep 17 00:00:00 2001 From: "Sigma Dimensions (Yass)" <79010126+myoualid@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:43:25 +0100 Subject: [PATCH] Resource Features : - Adding/removing and edit productivity data is now easier (+ cleaner UI) - Pressing calculate schedule work on a parent resource will default to calculating its nested resources schedule work. - Show derived Schedule Work for a parent resource - Improve apperance of Resource Tree Structure --- .../bim/module/resource/__init__.py | 1 + .../blenderbim/bim/module/resource/data.py | 14 +++ .../bim/module/resource/operator.py | 17 ++++ .../blenderbim/bim/module/resource/ui.py | 92 ++++++++++++------- src/blenderbim/blenderbim/core/resource.py | 28 +++--- src/blenderbim/blenderbim/tool/resource.py | 19 ++-- src/blenderbim/blenderbim/tool/sequence.py | 2 +- .../test/bim/feature/resource.feature | 2 + .../api/resource/calculate_resource_work.py | 21 +++-- .../ifcopenshell/util/date.py | 2 +- .../ifcopenshell/util/resource.py | 3 + .../ifcopenshell/util/sequence.py | 2 +- 12 files changed, 137 insertions(+), 66 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py index a1a840b172..8512a370dd 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py @@ -22,6 +22,7 @@ from . import ui, prop, operator classes = ( operator.AddResource, operator.AddResourceQuantity, + operator.AddProductivityData, operator.AssignResource, operator.CalculateResourceWork, operator.ConstrainResourceWork, diff --git a/src/blenderbim/blenderbim/bim/module/resource/data.py b/src/blenderbim/blenderbim/bim/module/resource/data.py index 18262ba5d1..146bb7ce00 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/data.py +++ b/src/blenderbim/blenderbim/bim/module/resource/data.py @@ -63,6 +63,7 @@ class ResourceData: productivity = cls.get_productivity(resource) if productivity: results[resource.id()]["Productivity"] = { + "id": productivity.get("id"), "QuantityProduced": ifcopenshell.util.resource.get_quantity_produced(productivity), "TimeConsumed": ifcopenshell.util.resource.get_unit_consumed(productivity), "QuantityProducedName": ifcopenshell.util.resource.get_quantity_produced_name(productivity), @@ -85,8 +86,21 @@ class ResourceData: results[resource.id()]["ScheduleUsage"] = ( resource.Usage.ScheduleUsage if resource.Usage.ScheduleUsage else None ) + if resource.IsNestedBy: + results[resource.id()]["DerivedScheduleWork"] = cls.sum_person_hours(resource) return results + @classmethod + def sum_person_hours(cls, resource): + sum = 0 + nested_resources = ifcopenshell.util.resource.get_nested_resources(resource) + for nested_resource in nested_resources or []: + if not nested_resource.Usage: + continue + duration = ifcopenshell.util.date.ifc2datetime(nested_resource.Usage.ScheduleWork) + sum += duration.total_seconds() / 3600 + return round(float(sum), 2) if sum else 0 + @classmethod def get_resource_benchmarks(cls, resource): constraints = [] diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 702e172067..b3eaea14b2 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -18,6 +18,7 @@ import bpy from bpy_extras.io_utils import ImportHelper +from blenderbim.bim.module.resource.ui import draw_productivity_ui import blenderbim.core.resource as core import blenderbim.tool as tool @@ -355,6 +356,16 @@ class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): core.import_resources(tool.Resource, file_path=self.filepath) +class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_productivity_data" + bl_description = "Apply" + bl_label = "Add Productivity" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + tool.Ifc.run("pset.add_pset", product=tool.Resource.get_highlighted_resource(), name="EPset_Productivity") + + class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_productivity_data" bl_description = "Apply" @@ -364,6 +375,12 @@ class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): core.edit_productivity_pset(tool.Ifc, tool.Resource) + def draw(self, context): + draw_productivity_ui(self, context) + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=600) + class ConstrainResourceWork(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_usage_constraint" diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index 55293cc196..9e7495db0f 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -65,21 +65,22 @@ class BIM_PT_resources(Panel): self.props, "active_resource_index", ) - self.draw_productivity_ui(context) - if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES": - self.draw_editable_resource_attributes_ui() - elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY": - self.draw_editable_resource_quantity_ui() - elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS": - self.draw_editable_resource_costs_ui() - elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE": - self.draw_editable_resource_time_attributes_ui() + if self.props.active_resource_id: + if self.props.editing_resource_type == "ATTRIBUTES": + self.draw_editable_resource_attributes_ui() + elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY": + self.draw_editable_resource_quantity_ui() + elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS": + self.draw_editable_resource_costs_ui() + elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE": + self.draw_editable_resource_time_attributes_ui() + self.draw_productivity_ui(context) def draw_productivity_ui(self, context): row = self.layout.row(align=True) row.alignment = "RIGHT" - row.prop(self.props, "should_show_resource_tools", text="Resource Tools",icon="RECOVER_LAST") + row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST") if self.props.should_show_resource_tools: total_resources = len(self.tprops.resources) if not total_resources or self.props.active_resource_index >= total_resources: @@ -120,7 +121,12 @@ class BIM_PT_resources(Panel): row1_col1 = col1.row() row1_col1.label(text="Schedule Work") row1col2 = col2.row() - row1col2.label(text=resource.get("ScheduleWork", "-"), icon="TIME") + schedule_work = resource.get("ScheduleWork", None) + derived_schedule_work = resource.get("DerivedScheduleWork", None) + row1col2.label( + text="{}".format(schedule_work) if schedule_work else "*{}".format(derived_schedule_work), + icon="TIME", + ) row1col3 = col3.row() row1col3.operator("bim.calculate_resource_work", text="", icon="TEMP").resource = ifc_definition_id @@ -156,6 +162,12 @@ class BIM_PT_resources(Panel): ) row.alignment = "LEFT" row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA") + row.operator("bim.edit_productivity_data", text="", icon="GREASEPENCIL") + op = row.operator("bim.remove_pset", text="", icon="X") + op.pset_id = productivity["id"] + op.obj_type = "Resource" + op.obj = "" + elif parent_productivity: produtivitiy_rate_message = "Inherited Productivity Rate: {} {} / {}*".format( parent_productivity["QuantityProduced"], @@ -163,30 +175,14 @@ class BIM_PT_resources(Panel): parent_productivity["TimeConsumed"], ) row.alignment = "LEFT" - row.label(text="Productivity: {}".format(produtivitiy_rate_message), icon="ARMATURE_DATA") + row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA") + row.operator("bim.add_productivity_data", text="", icon="ADD") else: row = self.layout.row(align=True) row.alignment = "LEFT" produtivitiy_rate_message = "No productivity data found" - row.label(text="Productivity: {}".format(produtivitiy_rate_message), icon="ARMATURE_DATA") - productivity_props = context.scene.BIMResourceProductivity - grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False) - col1 = grid.column(align=False) - col2 = grid.column(align=False) - col1.ui_units_x = 1 - col2.ui_units_x = 3 - row1_col1 = col1.row() - row1_col1.label(text="Quantity") - row1_col2 = col2.row() - row1_col2.prop(productivity_props, "quantity_produced", text="") - row1_col2.prop(productivity_props, "quantity_produced_name", text="") - row2_col1 = col1.row() - row2_col1.label(text="Time") - row2_col2 = col2.row() - self.draw_duration_property(productivity_props.quantity_consumed, row2_col2) - row3_col2 = col2.row() - row3_col2.alignment = "RIGHT" - row3_col2.operator("bim.edit_productivity_data", text="", icon="CHECKMARK") + row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA") + row.operator("bim.add_productivity_data", text="", icon="ADD") def draw_resource_operators(self): row = self.layout.row(align=True) @@ -231,6 +227,12 @@ class BIM_PT_resources(Panel): op.resource = ifc_definition_id row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id + else: + if self.props.editing_resource_type == "ATTRIBUTES": + row.operator("bim.edit_resource", text="", icon="CHECKMARK") + elif self.props.editing_resource_type == "USAGE": + row.operator("bim.edit_resource_time", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_resource", text="", icon="CANCEL") def draw_editable_resource_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.resource_attributes, self.layout) @@ -354,7 +356,7 @@ class BIM_UL_resources(UIList): else: row.label(text="", icon="DOT") row.prop(item, "name", emboss=False, text="", icon=icon_map[resource["type"]]) - row.prop(item, "schedule_usage", text="", emboss=False) + row.prop(item, "schedule_usage", text="", emboss=False) if item.schedule_usage else None if context.active_object and not props.active_resource_id: row = layout.row(align=True) if item.ifc_definition_id in ResourceData.data["active_resource_ids"]: @@ -370,3 +372,29 @@ class BIM_UL_resources(UIList): elif props.editing_resource_type == "USAGE": row.operator("bim.edit_resource_time", text="", icon="CHECKMARK") row.operator("bim.disable_editing_resource", text="", icon="CANCEL") + + +def draw_productivity_ui(self, context): + def draw_duration_property(duration_props, layout): + for duration_prop in duration_props: + if duration_prop.name == "BaseQuantityConsumed": + layout.prop(duration_prop, "years", text="Y") + layout.prop(duration_prop, "months", text="M") + layout.prop(duration_prop, "days", text="D") + layout.prop(duration_prop, "hours", text="H") + layout.prop(duration_prop, "minutes", text="Min") + layout.prop(duration_prop, "seconds", text="S") + + productivity_props = context.scene.BIMResourceProductivity + grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False) + col1 = grid.column(align=False) + col2 = grid.column(align=False) + row1_col1 = col1.row() + row1_col1.label(text="Quantity") + row1_col2 = col2.row() + row1_col2.prop(productivity_props, "quantity_produced", text="") + row1_col2.prop(productivity_props, "quantity_produced_name", text="") + row2_col1 = col1.row() + row2_col1.label(text="Time") + row2_col2 = col2.row() + draw_duration_property(productivity_props.quantity_consumed, row2_col2) diff --git a/src/blenderbim/blenderbim/core/resource.py b/src/blenderbim/blenderbim/core/resource.py index b265d9326b..6b1ee21719 100644 --- a/src/blenderbim/blenderbim/core/resource.py +++ b/src/blenderbim/blenderbim/core/resource.py @@ -26,8 +26,7 @@ def load_resources(resource): def add_resource(tool_ifc, resource_tool, ifc_class, parent_resource=None): tool_ifc.run("resource.add_resource", ifc_class=ifc_class, parent_resource=parent_resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() + load_resources(resource_tool) def load_resource_properties(resource_tool, resource=None): @@ -56,8 +55,7 @@ def edit_resource(ifc, resource_tool, resource): def remove_resource(ifc, resource_tool, resource=None): ifc.run("resource.remove_resource", resource=resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() + load_resources(resource_tool) def enable_editing_resource_time(ifc_tool, resource_tool, resource): @@ -79,9 +77,13 @@ def disable_editing_resource_time(resource_tool): def calculate_resource_work(ifc, resource_tool, resource): - ifc.run("resource.calculate_resource_work", resource=resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() + if resource_tool.get_task_assignments(resource): + ifc.run("resource.calculate_resource_work", resource=resource) + else: + nested_resources = resource_tool.get_nested_resources(resource) + for nested_resource in nested_resources or []: + ifc.run("resource.calculate_resource_work", resource=nested_resource) + load_resources(resource_tool) def enable_editing_resource_costs(resource_tool, resource): @@ -142,20 +144,17 @@ def edit_resource_quantity(resource_tool, ifc, physical_quantity=None): def import_resources(resource_tool, file_path): resource_tool.import_resources(file_path) - resource_tool.load_resources() - resource_tool.load_resource_properties() + load_resources(resource_tool) def expand_resource(resource_tool, resource): resource_tool.expand_resource(resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() + load_resources(resource_tool) def contract_resource(resource_tool, resource): resource_tool.contract_resource(resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() + load_resources(resource_tool) def assign_resource(ifc, spatial, resource=None, products=None): @@ -220,5 +219,4 @@ def go_to_resource(resource_tool, resource): def calculate_resource_usage(ifc, resource_tool, resource): ifc.run("resource.calculate_resource_usage", resource=resource) - resource_tool.load_resources() - resource_tool.load_resource_properties() \ No newline at end of file + load_resources(resource_tool) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/resource.py b/src/blenderbim/blenderbim/tool/resource.py index 2b820dcbc3..13d51f8d05 100644 --- a/src/blenderbim/blenderbim/tool/resource.py +++ b/src/blenderbim/blenderbim/tool/resource.py @@ -55,12 +55,13 @@ class Resource(blenderbim.core.tool.Resource): tprops = bpy.context.scene.BIMResourceTreeProperties tprops.resources.clear() contracted_resources = json.loads(props.contracted_resources) - + props.is_resource_update_enabled = False for resource in tool.Ifc.get().by_type("IfcResource"): if not resource.HasContext: continue create_new_resource_li(resource, 0) cls.load_productivity_data() + props.is_resource_update_enabled = True props.is_editing = True @classmethod @@ -71,7 +72,7 @@ class Resource(blenderbim.core.tool.Resource): for item in tprops.resources: resource = tool.Ifc.get().by_id(item.ifc_definition_id) item.name = resource.Name if resource.Name else "Unnamed" - item.schedule_usage = resource.Usage.ScheduleUsage or 1 if resource.Usage else 0 + item.schedule_usage = resource.Usage.ScheduleUsage if (resource.Usage and resource.Usage.ScheduleUsage) else 0 props.is_resource_update_enabled = True @classmethod @@ -374,13 +375,11 @@ class Resource(blenderbim.core.tool.Resource): @classmethod def edit_productivity_pset(cls, resource, attributes): productivity = cls.get_productivity(resource) - if productivity: - pset = tool.Ifc.get().by_id(productivity["id"]) - else: - pset = tool.Ifc.run("pset.add_pset", product=resource, name="EPset_Productivity") - tool.Ifc.run( + if not productivity: + return + return tool.Ifc.run( "pset.edit_pset", - pset=pset, + pset= tool.Ifc.get().by_id(productivity["id"]), properties=attributes, ) @@ -444,3 +443,7 @@ class Resource(blenderbim.core.tool.Resource): @classmethod def get_task_assignments(cls, resource): return ifcopenshell.util.resource.get_task_assignments(resource) + + @classmethod + def get_nested_resources(cls, resource): + return ifcopenshell.util.resource.get_nested_resources(resource) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index 2491b245d0..e7f924b72f 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -416,7 +416,7 @@ class Sequence(blenderbim.core.tool.Sequence): new = props.task_resources.add() new.ifc_definition_id = resource.id() new.name = resource.Name or "Unnamed" - new.schedule_usage = resource.Usage.ScheduleUsage or 1 if resource.Usage else 0 + new.schedule_usage = resource.Usage.ScheduleUsage or 0 if resource.Usage else 0 @classmethod def load_resources(cls): diff --git a/src/blenderbim/test/bim/feature/resource.feature b/src/blenderbim/test/bim/feature/resource.feature index 71af9ae05a..3e01cfa926 100644 --- a/src/blenderbim/test/bim/feature/resource.feature +++ b/src/blenderbim/test/bim/feature/resource.feature @@ -249,6 +249,7 @@ Scenario: Add Productivity data When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})" And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()" And I set "scene.BIMResourceProperties.active_resource_index" to "1" + And I press "bim.add_productivity_data" And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True" And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00" And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea" @@ -286,6 +287,7 @@ Scenario: Calculate Resource Work When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})" And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()" And I set "scene.BIMResourceProperties.active_resource_index" to "1" + And I press "bim.add_productivity_data" And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True" And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00" And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea" diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py b/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py index c8638e846c..ac978573e0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py @@ -60,14 +60,7 @@ class Usecase: self.settings = {"resource": resource} def execute(self): - metrics = ifcopenshell.util.constraint.has_metric_constraints( - self.settings["resource"], "Usage.ScheduleWork" - ) - if ( - metrics - and metrics[0].ConstraintGrade == "HARD" - and metrics[0].Benchmark == "EQUALTO" - ): + if self.has_hard_constraint(): return amount_worked = ifcopenshell.util.resource.get_resource_required_work( self.settings["resource"] @@ -81,3 +74,15 @@ class Usecase: resource=self.settings["resource"], ) self.settings["resource"].Usage.ScheduleWork = amount_worked + + def has_hard_constraint(self): + metrics = ifcopenshell.util.constraint.has_metric_constraints( + self.settings["resource"], "Usage.ScheduleWork" + ) + if ( + metrics + and metrics[0].ConstraintGrade == "HARD" + and metrics[0].Benchmark == "EQUALTO" + ): + return True + return False diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index 8f7c45c054..27e6df4af0 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -98,7 +98,7 @@ def readable_ifc_duration(string): final_string += f"{months} m " if months else "" final_string += f"{weeks} w " if weeks else "" final_string += f"{days} d " if days else "" - final_string += f"{hours} h " if hours else "" + final_string += f"{round(float(hours),2)} h " if hours else "" final_string += f"{minutes} m " if minutes else "" final_string += f"{seconds} s " if seconds else "" diff --git a/src/ifcopenshell-python/ifcopenshell/util/resource.py b/src/ifcopenshell-python/ifcopenshell/util/resource.py index f4ce388dc5..ad75887361 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/resource.py +++ b/src/ifcopenshell-python/ifcopenshell/util/resource.py @@ -112,3 +112,6 @@ def get_resource_required_work(resource): required_work = total_quantity_to_produce * productivity_ratio iso_string = f"P{required_work}D" return iso_string + +def get_nested_resources(resource): + return [object for rel in resource.IsNestedBy or [] for object in rel.RelatedObjects] \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/util/sequence.py b/src/ifcopenshell-python/ifcopenshell/util/sequence.py index 27db7330d5..02ec945a63 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/util/sequence.py @@ -248,7 +248,7 @@ def get_task_work_schedule(task): def get_nested_tasks(task): - return [object for rel in task.IsNestedBy for object in rel.RelatedObjects] + return [object for rel in task.IsNestedBy or [] for object in rel.RelatedObjects] def get_parent_task(task):