diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index c3a4eb3788..d4e26a310c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -97,6 +97,7 @@ classes = ( operator.RemoveWorkPlan, operator.RemoveWorkSchedule, operator.RemoveWorkTime, + operator.ReorderTask, operator.SelectTaskRelatedProducts, operator.SelectTaskRelatedInputs, operator.SelectWorkScheduleProducts, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/data.py b/src/blenderbim/blenderbim/bim/module/sequence/data.py index f414efde44..457cdff139 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/data.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/data.py @@ -223,6 +223,9 @@ class SequenceData: if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl: if rel.RelatingControl.is_a("IfcWorkCalendar"): data["HasAssignmentsWorkCalendar"].append(rel.RelatingControl.id()) + data["NestingIndex"] = None + for rel in task.Nests or []: + data["NestingIndex"] = rel.RelatedObjects.index(task) cls.data["tasks"][task.id()] = data @classmethod diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 6ac4d4c926..21f113fa68 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -275,13 +275,13 @@ class EditTaskTime(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingTask(bpy.types.Operator): - bl_idname = "bim.enable_editing_task" - bl_label = "Enable Editing Task" + bl_idname = "bim.enable_editing_task_attributes" + bl_label = "Enable Editing Task Attributes" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() def execute(self, context): - core.enable_editing_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) + core.enable_editing_task_attributes(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} @@ -1295,7 +1295,7 @@ class CopyTask(bpy.types.Operator, tool.Ifc.Operator): class LoadProductTasks(bpy.types.Operator): - bl_idname = "bim.load_product_tasks" + bl_idname = "bim.load_product_related_tasks" bl_label = "Load Product Tasks" bl_options = {"REGISTER", "UNDO"} @@ -1310,7 +1310,7 @@ class LoadProductTasks(bpy.types.Operator): return True def execute(self, context): - core.load_product_tasks( + core.load_product_related_tasks( tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id) ) return {"FINISHED"} @@ -1355,3 +1355,18 @@ class SelectUnassignedWorkScheduleProducts(bpy.types.Operator): if isinstance(r, str): self.report({"WARNING"}, r) return {"FINISHED"} + + +class ReorderTask(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.reorder_task_nesting" + bl_label = "Reorder Nesting" + bl_options = {"REGISTER", "UNDO"} + new_index: bpy.props.IntProperty() + task: bpy.props.IntProperty() + + def _execute(self, context): + r = core.reorder_task_nesting( + tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task), new_index=self.new_index + ) + if isinstance(r, str): + self.report({"WARNING"}, r) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index a80b935ab6..6fd241730d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -284,9 +284,14 @@ def update_color_progress(self, context): color[1] = color_progress.g color[2] = color_progress.b + def update_sort_reversed(self, context): if context.scene.BIMWorkScheduleProperties.active_work_schedule_id: - core.create_task_tree(tool.Sequence, work_schedule=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_work_schedule_id)) + core.load_task_tree( + tool.Sequence, + work_schedule=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_work_schedule_id), + ) + class Task(PropertyGroup): name: StringProperty(name="Name", update=updateTaskName) @@ -359,7 +364,8 @@ class BIMWorkScheduleProperties(PropertyGroup): active_task_id: IntProperty(name="Active Task Id") task_attributes: CollectionProperty(name="Task Attributes", type=Attribute) should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False) - should_show_bar_visual_option: BoolProperty(name="Add to task bar", default=False) + should_show_task_bar_selection: BoolProperty(name="Add to task bar", default=False) + should_show_snapshot_ui: BoolProperty(name="Should Show Snapshot UI", default=False) should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False) columns: CollectionProperty(name="Columns", type=Attribute) active_column_index: IntProperty(name="Active Column Index") @@ -417,6 +423,8 @@ class BIMWorkScheduleProperties(PropertyGroup): product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct) active_product_output_task_index: IntProperty(name="Active Product Output Task Index") active_product_input_task_index: IntProperty(name="Active Product Input Task Index") + enable_reorder: BoolProperty(name="Enable Reorder", default=False) + show_task_operators: BoolProperty(name="Show Task Options", default=True) class BIMTaskTreeProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index e785d03629..07fc718680 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -170,7 +170,9 @@ class BIM_PT_work_schedules(Panel): row1 = col.row(align=True) row1.alignment = "RIGHT" row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY") - row1.prop(self.props, "should_show_visualisation_ui", text="Animation Options", icon="CAMERA_STEREO") + row2 = col.row(align=True) + row2.prop(self.props, "should_show_visualisation_ui", text="Animation Options", icon="CAMERA_STEREO") + row2.prop(self.props, "should_show_snapshot_ui", text="Snapshot Options", icon="CAMERA_STEREO") row.operator("bim.disable_editing_work_schedule", text="Disable editing", icon="CANCEL") else: row.operator( @@ -189,6 +191,8 @@ class BIM_PT_work_schedules(Panel): self.draw_column_ui() if self.props.should_show_visualisation_ui: self.draw_visualisation_ui() + if self.props.should_show_snapshot_ui: + self.draw_snapshot_ui() self.draw_editable_task_ui(work_schedule_id) def draw_task_operators(self): @@ -206,14 +210,23 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.edit_task", text="", icon="CHECKMARK") row.operator("bim.disable_editing_task", text="", icon="CANCEL") else: - row.prop(self.props, "should_show_bar_visual_option", text="", icon="NLA_PUSHDOWN") - row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = ifc_definition_id - row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id - row.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = ifc_definition_id - row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = ifc_definition_id - row.operator("bim.add_task", text="", icon="ADD").task = ifc_definition_id - row.operator("bim.duplicate_task", text="", icon="DUPLICATE").task = ifc_definition_id - row.operator("bim.remove_task", text="", icon="X").task = ifc_definition_id + row.prop(self.props, "show_task_operators", text="Edit", icon="GREASEPENCIL") + if self.props.show_task_operators: + row2 = self.layout.row(align=True) + row2.alignment = "RIGHT" + + row2.prop(self.props, "enable_reorder", text="", icon="SORTALPHA") + row2.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = ifc_definition_id + row2.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id + row2.operator( + "bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO" + ).task = ifc_definition_id + row2.operator( + "bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL" + ).task = ifc_definition_id + row.operator("bim.add_task", text="Add", icon="ADD").task = ifc_definition_id + row.operator("bim.duplicate_task", text="Copy", icon="DUPLICATE").task = ifc_definition_id + row.operator("bim.remove_task", text="Delete", icon="X").task = ifc_definition_id def draw_column_ui(self): row = self.layout.row() @@ -281,26 +294,35 @@ class BIM_PT_work_schedules(Panel): row.prop(self.animation_props, "should_show_task_bar_options", text="Task Bar", icon="NLA_PUSHDOWN") if self.animation_props.should_show_task_bar_options: row = self.layout.row() - row.operator("bim.add_task_bars", text="Add Bar Visual", icon="NLA_PUSHDOWN") + row.label(text="Task Bar Options", icon="NLA_PUSHDOWN") + row.alignment = "LEFT" + row = self.layout.row(align=True) + row.prop(self.props, "should_show_task_bar_selection", text="Enable Selection", icon="NLA_PUSHDOWN") + row.operator("bim.add_task_bars", text="Generate bars", icon="NLA_PUSHDOWN") grid = self.layout.grid_flow(columns=2, even_columns=True) # Column1 col = grid.column() - row1 = col.row(align=True) - row1.label(text="Bar Colors", icon="NLA_PUSHDOWN") - row2 = col.row(align=True) - row2.prop(self.animation_props, "color_progress") + row = col.row(align=True) + row.prop(self.animation_props, "color_progress") - row3 = col.row(align=True) - row3.prop(self.animation_props, "color_full") + row = col.row(align=True) + row.prop(self.animation_props, "color_full") if self.animation_props.is_editing: self.draw_visualisation_settings_ui() - row = self.layout.row() + row = self.layout.row(align=True) op = row.operator("bim.visualise_work_schedule_date_range", text="Create Animation", icon="OUTLINER_OB_CAMERA") op.work_schedule = self.props.active_work_schedule_id + + def draw_snapshot_ui(self): + row = self.layout.row(align=True) + row.label(text="Create Construction Snapshot:") + row = self.layout.row(align=True) + op = row.operator("bim.datepicker", text=self.props.visualisation_start or "Date", icon="REW") + op.target_prop = "BIMWorkScheduleProperties.visualisation_start" op = row.operator("bim.visualise_work_schedule_date", text="Create SnapShot", icon="RESTRICT_RENDER_OFF") op.work_schedule = self.props.active_work_schedule_id @@ -572,7 +594,7 @@ class BIM_UL_task_resources(UIList): if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") - row.prop(item, "schedule_usage", emboss=False, text="") + row.label(text=str(item.schedule_usage)) class BIM_UL_animation_colors(UIList): @@ -636,7 +658,7 @@ class BIM_UL_tasks(UIList): text="", emboss=False, ) - if self.props.should_show_bar_visual_option: + if self.props.should_show_task_bar_selection: row.prop( item, "has_bar_visual", @@ -644,6 +666,8 @@ class BIM_UL_tasks(UIList): text="", emboss=False, ) + if self.props.enable_reorder: + self.draw_order_operator(row, item.ifc_definition_id) if self.props.active_task_id: if self.props.editing_task_type == "SEQUENCE" and self.props.active_task_id != item.ifc_definition_id: if item.is_predecessor: @@ -658,6 +682,18 @@ class BIM_UL_tasks(UIList): op = row.operator("bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False) op.task = item.ifc_definition_id + def draw_order_operator(self, row, ifc_definition_id): + task = SequenceData.data["tasks"][ifc_definition_id] + if task["NestingIndex"] is not None: + if task["NestingIndex"] == 0: + op = row.operator("bim.reorder_task_nesting", icon="TRIA_DOWN", text="") + op.task = ifc_definition_id + op.new_index = task["NestingIndex"] + 1 + elif task["NestingIndex"] > 0: + op = row.operator("bim.reorder_task_nesting", icon="TRIA_UP", text="") + op.task = ifc_definition_id + op.new_index = task["NestingIndex"] - 1 + def draw_hierarchy(self, row, item): for i in range(0, item.level_index): row.label(text="", icon="BLANK1") @@ -878,7 +914,7 @@ class BIM_PT_4D_Tools(Panel): def draw(self, context): self.props = context.scene.BIMWorkScheduleProperties row = self.layout.row() - row.operator("bim.load_product_tasks", text="Load Tasks", icon="FILE_REFRESH") + row.operator("bim.load_product_related_tasks", text="Load Tasks", icon="FILE_REFRESH") grid = self.layout.grid_flow(columns=2, even_columns=True) col1 = grid.column() diff --git a/src/blenderbim/blenderbim/core/resource.py b/src/blenderbim/blenderbim/core/resource.py index 840a6c24d6..0beafa9b9b 100644 --- a/src/blenderbim/blenderbim/core/resource.py +++ b/src/blenderbim/blenderbim/core/resource.py @@ -170,3 +170,15 @@ def unassign_resource(ifc, spatial, resource=None, products=None): products = spatial.get_selected_products() for product in products: ifc.run("resource.unassign_resource", relating_resource=resource, related_object=product) + + +def edit_productivity_pset(ifc, resource_tool): + resource = resource_tool.get_highlighted_resource() + if resource is None: + return + productivity = resource_tool.get_productivity(resource) + if productivity: + pset = ifc.get().by_id(productivity["id"]) + else: + pset = ifc.run("pset.add_pset", product=resource, name="EPset_Productivity") + ifc.run("pset.edit_pset", pset=pset, properties=resource_tool.get_productivity_attributes()) diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index 8cc4e26f4d..3f1a884867 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -74,47 +74,47 @@ def enable_editing_work_schedule(sequence, work_schedule=None): def enable_editing_work_schedule_tasks(sequence, work_schedule=None): sequence.enable_editing_work_schedule_tasks(work_schedule) - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() -def create_task_tree(sequence, work_schedule): - sequence.create_task_tree(work_schedule) +def load_task_tree(sequence, work_schedule): + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def expand_task(sequence, task=None): sequence.expand_task(task) work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def expand_all_tasks(sequence): sequence.expand_all_tasks() work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def contract_task(sequence, task=None): sequence.contract_task(task) work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def contract_all_tasks(sequence): sequence.contract_all_tasks() work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def remove_task(ifc, sequence, task=None): ifc.run("sequence.remove_task", task=task) work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() sequence.disable_selecting_deleted_task() @@ -129,21 +129,24 @@ def disable_editing_work_schedule(sequence): def add_summary_task(ifc, sequence, work_schedule=None): ifc.run("sequence.add_task", work_schedule=work_schedule) - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def add_task(ifc, sequence, parent_task=None): ifc.run("sequence.add_task", parent_task=parent_task) work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() def enable_editing_task(sequence, task=None): - sequence.load_task_attributes(task) sequence.enable_editing_task(task) +def enable_editing_task_attributes(sequence, task=None): + sequence.load_task_attributes(task) + sequence.enable_editing_task_attributes(task) + def edit_task(ifc, sequence, task=None): attributes = sequence.get_task_attributes() @@ -165,7 +168,7 @@ def copy_task_attribute(ifc, sequence, attribute_name=None): def duplicate_task(ifc, sequence, task=None): ifc.run("sequence.duplicate_task", task=task) work_schedule = sequence.get_active_work_schedule() - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() @@ -464,7 +467,7 @@ def calculate_task_duration(ifc, sequence, task=None): ifc.run("sequence.calculate_task_duration", task=task) work_schedule = sequence.get_active_work_schedule() if work_schedule: - sequence.create_task_tree(work_schedule) + sequence.load_task_tree(work_schedule) sequence.load_task_properties() @@ -473,6 +476,8 @@ def highlight_task(sequence, task=None): is_work_schedule_active = sequence.is_work_schedule_active(work_schedule) if is_work_schedule_active: sequence.highlight_task(task) + else: + return "Work schedule is not active" def highlight_product_related_task(sequence, spatial, product_type=None): @@ -537,5 +542,17 @@ def generate_gantt_chart(sequence, work_schedule): sequence.generate_gantt_browser_chart(json) -def load_product_tasks(sequence, product=None): - sequence.load_product_tasks(product) +def load_product_related_tasks(sequence, product=None): + sequence.load_product_related_tasks(product) + + +def reorder_task_nesting(ifc, sequence, task, new_index): + is_sorting_enabled = sequence.is_sorting_enabled() + is_sort_reversed = sequence.is_sort_reversed() + if is_sorting_enabled or is_sort_reversed: + return "Remove manual sorting" + else: + ifc.run("nest.reorder_nesting", item= task, new_index= new_index) + work_schedule= sequence.get_active_work_schedule() + sequence.load_task_tree(work_schedule) + sequence.load_task_properties() \ No newline at end of file diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 7eebc0eb51..ae7ffd9662 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -562,32 +562,37 @@ class Qto: @interface class Resource: - def load_resources(cls): pass - def load_resource_properties(cls): pass + def clear_productivity_data(cls, props): pass + def contract_resource(cls, resource): pass + def disable_editing_resource_cost_value(cls): pass + def disable_editing_resource_quantity(cls): pass def disable_editing_resource(cls): pass def disable_resource_editing_ui(cls): pass - def load_resource_attributes(cls, resource): pass - def enable_editing_resource(cls, resource): pass - def get_resource_attributes(cls): pass - def enable_editing_resource_time(cls, resource): pass - def get_resource_time(cls, resource): pass - def load_resource_time_attributes(cls, resource_time): pass - def get_resource_time_attributes(cls): pass - def enable_editing_resource_costs(cls, resource): pass - def disable_editing_resource_cost_value(cls): pass - def enable_editing_resource_cost_value_formula(cls, cost_value): pass - def load_cost_value_attributes(cls, cost_value): pass + def edit_productivity_pset(cls, resource, attributes): pass def enable_editing_cost_value_attributes(cls, cost_value): pass - def get_resource_cost_value_formula(cls): pass - def get_resource_cost_value_attributes(cls): pass def enable_editing_resource_base_quantity(cls, resource): pass + def enable_editing_resource_cost_value_formula(cls, cost_value): pass + def enable_editing_resource_costs(cls, resource): pass def enable_editing_resource_quantity(cls, resource_quantity): pass - def disable_editing_resource_quantity(cls): pass - def get_resource_quantity_attributes(cls): pass + def enable_editing_resource_time(cls, resource): pass + def enable_editing_resource(cls, resource): pass def expand_resource(cls, resource): pass - def contract_resource(cls, resource): pass + def get_highlighted_resource(cls): pass + def get_productivity_attributes(cls): pass + def get_productivity(cls, resource, should_inherit): pass + def get_resource_attributes(cls): pass + def get_resource_cost_value_attributes(cls): pass + def get_resource_cost_value_formula(cls): pass + def get_resource_quantity_attributes(cls): pass + def get_resource_time_attributes(cls): pass + def get_resource_time(cls, resource): pass def import_resources(cls, file_path): pass - + def load_cost_value_attributes(cls, cost_value): pass + def load_productivity_data(cls): pass + def load_resource_attributes(cls, resource): pass + def load_resource_properties(cls): pass + def load_resource_time_attributes(cls, resource_time): pass + def load_resources(cls): pass @interface class Root: @@ -635,7 +640,7 @@ class Sequence: def contract_task(cls, task): pass def create_bars(cls, tasks): pass def create_new_task_json(cls, task, json, type_map=None): pass - def create_task_tree(cls, work_schedule): pass + def load_task_tree(cls, work_schedule): pass def create_tasks_json(cls, work_schedule=None): pass def disable_editing_rel_sequence(cls): pass def disable_editing_task_animation_colors(cls): pass @@ -653,7 +658,7 @@ class Sequence: def enable_editing_task_calendar(cls, task): pass def enable_editing_task_sequence(cls, task): pass def enable_editing_task_time(cls, task): pass - def enable_editing_task(cls, task): pass + def enable_editing_task_attributes(cls, task): pass def enable_editing_work_calendar_times(cls, work_calendar): pass def enable_editing_work_calendar(cls, work_calendar): pass def enable_editing_work_plan_schedules(cls, work_plan): pass @@ -700,7 +705,7 @@ class Sequence: def highlight_task(cls, task): pass def is_work_schedule_active(cls, work_schedule): pass def load_lag_time_attributes(cls, lag_time): pass - def load_product_tasks(cls, product): pass + def load_product_related_tasks(cls, product): pass def load_rel_sequence_attributes(cls, rel_sequence): pass def load_resources(cls): pass def load_task_animation_colors(cls): pass diff --git a/src/blenderbim/blenderbim/tool/resource.py b/src/blenderbim/blenderbim/tool/resource.py index e84a8237c5..b14556580a 100644 --- a/src/blenderbim/blenderbim/tool/resource.py +++ b/src/blenderbim/blenderbim/tool/resource.py @@ -30,6 +30,8 @@ from datetime import datetime from dateutil import parser import ifcopenshell.util.date as ifcdateutils import ifcopenshell.util.cost +import ifcopenshell.util.resource +import blenderbim.bim.schema class Resource(blenderbim.core.tool.Resource): @@ -58,6 +60,7 @@ class Resource(blenderbim.core.tool.Resource): if not resource.HasContext: continue create_new_resource_li(resource, 0) + cls.load_productivity_data() props.is_editing = True @classmethod @@ -299,8 +302,83 @@ class Resource(blenderbim.core.tool.Resource): @classmethod def get_highlighted_resource(cls): - return tool.Ifc.get().by_id( - bpy.context.scene.BIMResourceTreeProperties.resources[ - bpy.context.scene.BIMResourceProperties.active_resource_index + resources = len(bpy.context.scene.BIMResourceTreeProperties.resources) + if resources and resources > bpy.context.scene.BIMResourceProperties.active_resource_index: + return tool.Ifc.get().by_id( + bpy.context.scene.BIMResourceTreeProperties.resources[ + bpy.context.scene.BIMResourceProperties.active_resource_index ].ifc_definition_id - ) \ No newline at end of file + ) + + @classmethod + def clear_productivity_data(cls, props): + for duration_prop in props.quantity_consumed or []: + if duration_prop.name == "BaseQuantityConsumed": + duration_prop.years = 0 + duration_prop.months = 0 + duration_prop.days = 0 + duration_prop.hours = 0 + duration_prop.minutes = 0 + duration_prop.seconds = 0 + props.quantity_produced = 0 + props.quantity_produced_name = "" + + @classmethod + def load_productivity_data(cls): + duration_props = None + for collection_prop in bpy.context.scene.BIMResourceProductivity.quantity_consumed: + duration_props = collection_prop if collection_prop.name == "BaseQuantityConsumed" else None + break + if not duration_props: + duration_props = bpy.context.scene.BIMResourceProductivity.quantity_consumed.add() + duration_props.name = "BaseQuantityConsumed" + cls.clear_productivity_data(bpy.context.scene.BIMResourceProductivity) + current_resource = tool.Resource.get_highlighted_resource() + if current_resource: + productivity = cls.get_productivity(current_resource) + print("Resource:", current_resource, "productivity", productivity) + if productivity: + bpy.context.scene.BIMResourceProductivity.quantity_produced = ( + ifcopenshell.util.resource.get_quantity_produced(productivity) + ) + bpy.context.scene.BIMResourceProductivity.quantity_produced_name = ( + ifcopenshell.util.resource.get_quantity_produced_name(productivity) + ) + time_consumed = ifcopenshell.util.resource.get_unit_consumed(productivity) + if time_consumed: + durations_attributes = helper.parse_duration_as_blender_props(time_consumed) + duration_props.years = durations_attributes["years"] + duration_props.months = durations_attributes["months"] + duration_props.days = durations_attributes["days"] + duration_props.hours = durations_attributes["hours"] + duration_props.minutes = durations_attributes["minutes"] + duration_props.seconds = durations_attributes["seconds"] + + @classmethod + def get_productivity_attributes(cls): + props = bpy.context.scene.BIMResourceProductivity + productivity = {} + if props.quantity_consumed: + productivity["BaseQuantityConsumed"] = helper.simplify_duration( + props.quantity_consumed, "ELAPSEDTIME", "BaseQuantityConsumed" + ) + productivity["BaseQuantityProducedValue"] = props.quantity_produced + productivity["BaseQuantityProducedName"] = props.quantity_produced_name + return productivity + + @classmethod + def get_productivity(cls, resource, should_inherit=False): + return ifcopenshell.util.resource.get_productivity(resource, should_inherit=should_inherit) + + @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( + "pset.edit_pset", + pset=pset, + properties=attributes, + ) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index 138f35bc9d..e02d28eb3f 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -27,6 +27,7 @@ import ifcopenshell.util.element import ifcopenshell.util.unit import json import blenderbim.core.tool +import blenderbim.core import blenderbim.tool as tool import blenderbim.bim.helper import blenderbim.bim.module.sequence.helper as helper @@ -132,7 +133,7 @@ class Sequence(blenderbim.core.tool.Sequence): props.editing_type = "TASKS" @classmethod - def create_task_tree(cls, work_schedule): + def load_task_tree(cls, work_schedule): bpy.context.scene.BIMTaskTreeProperties.tasks.clear() props = bpy.context.scene.BIMWorkScheduleProperties cls.contracted_tasks = json.loads(props.contracted_tasks) @@ -145,25 +146,26 @@ class Sequence(blenderbim.core.tool.Sequence): def get_sorted_tasks_ids(cls, tasks): 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 "") - if not bpy.context.scene.BIMWorkScheduleProperties.sort_column: - return task.Identification or "" + # 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(".") if column_type == "IfcTask": - return task.Name or "" + return task.get_info(task)[name] or "" elif column_type == "IfcTaskTime" and task.TaskTime: - return task.TaskTime.Name or "" + return task.TaskTime.get_info(task)[name] return task.Identification or "" def natural_sort_key(i, _nsre=re.compile("([0-9]+)")): s = sort_keys[i] return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] - sort_keys = {task.id(): get_sort_key(task) for task in tasks} - related_object_ids = sorted(sort_keys, key=natural_sort_key) + if bpy.context.scene.BIMWorkScheduleProperties.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: related_object_ids.reverse() return related_object_ids @@ -328,7 +330,7 @@ class Sequence(blenderbim.core.tool.Sequence): blenderbim.bim.helper.import_attributes2(task, props.task_attributes) @classmethod - def enable_editing_task(cls, task): + def enable_editing_task_attributes(cls, task): props = bpy.context.scene.BIMWorkScheduleProperties props.active_task_id = task.id() props.editing_task_type = "ATTRIBUTES" @@ -407,16 +409,15 @@ class Sequence(blenderbim.core.tool.Sequence): def load_task_resources(cls, resources): props = bpy.context.scene.BIMWorkScheduleProperties props.task_resources.clear() - if resources: - for resource in resources: - 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 + for resource in resources or []: + 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 @classmethod def load_resources(cls): - bpy.ops.bim.load_resources() # remove and refactor + blenderbim.core.resource.load_resources(tool.Resource) @classmethod def get_task_inputs(cls, task): @@ -428,10 +429,22 @@ class Sequence(blenderbim.core.tool.Sequence): is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs return ifcopenshell.util.sequence.get_task_outputs(task, is_deep) + @classmethod + def are_entities_same_class(cls, entities): + if not entities: + return False + if len(entities) == 1: + return True + first = entities[0] + for entity in entities: + if entity.is_a() != first.is_a(): + return False + return True + @classmethod def get_task_resources(cls, task): is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources - return ifcopenshell.util.sequence.get_task_resource(task, is_deep) + return ifcopenshell.util.sequence.get_task_resources(task, is_deep) @classmethod def load_task_inputs(cls, inputs): @@ -732,6 +745,8 @@ class Sequence(blenderbim.core.tool.Sequence): def remove_task_column(cls, name): props = bpy.context.scene.BIMWorkScheduleProperties props.columns.remove(props.columns.find(name)) + if props.sort_column == name: + props.sort_column = "" @classmethod def set_task_sort_column(cls, column): @@ -779,7 +794,7 @@ class Sequence(blenderbim.core.tool.Sequence): bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks) expand_ancestors(parent_task) work_schedule = cls.get_active_work_schedule() - cls.create_task_tree(work_schedule) + cls.load_task_tree(work_schedule) cls.load_task_properties() task_props = bpy.context.scene.BIMTaskTreeProperties @@ -1510,9 +1525,8 @@ class Sequence(blenderbim.core.tool.Sequence): webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html")) @classmethod - def load_product_tasks(cls, product): + def load_product_related_tasks(cls, product): props = bpy.context.scene.BIMWorkScheduleProperties - props.is_task_update_enabled = False props.product_input_tasks.clear() props.product_output_tasks.clear() task_inputs, task_ouputs = ifcopenshell.util.sequence.get_tasks_for_product(product) @@ -1540,3 +1554,11 @@ class Sequence(blenderbim.core.tool.Sequence): task_inputs = [task for task in task_inputs or [] if cls.get_work_schedule(task) == work_schedule] task_ouputs = [task for task in task_ouputs or [] if cls.get_work_schedule(task) == work_schedule] return bool(task_inputs or task_ouputs) + + @classmethod + def is_sorting_enabled(cls): + return bpy.context.scene.BIMWorkScheduleProperties.sort_column + + @classmethod + def is_sort_reversed(cls): + return bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed diff --git a/src/blenderbim/test/bim/feature/sequence.feature b/src/blenderbim/test/bim/feature/sequence.feature index 5f1612c518..1015ef660e 100644 --- a/src/blenderbim/test/bim/feature/sequence.feature +++ b/src/blenderbim/test/bim/feature/sequence.feature @@ -225,7 +225,7 @@ Scenario: Enable editing task And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - When I press "bim.enable_editing_task(task={task})" + When I press "bim.enable_editing_task_attributes(task={task})" Then nothing happens Scenario: Copy task attribute @@ -236,7 +236,7 @@ Scenario: Copy task attribute And I press "bim.add_summary_task(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes[2].string_value" to "Foo" When I set "scene.BIMTaskTreeProperties.tasks[1].is_selected" to "True" And I press "bim.copy_task_attribute(name='Description')" @@ -253,7 +253,7 @@ Scenario: Unassign task Successor And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" And I press "bim.assign_successor(task={nested_task_two})" When I press "bim.unassign_successor(task={nested_task_two})" Then nothing happens @@ -269,7 +269,7 @@ Scenario: Edit time Lag And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" When I press "bim.assign_successor(task={nested_task_two})" And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" When I press "bim.assign_lag_time(sequence={rel_sequence})" @@ -290,7 +290,7 @@ Scenario: Unassign time Lag And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" When I press "bim.assign_successor(task={nested_task_two})" And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" When I press "bim.assign_lag_time(sequence={rel_sequence})" @@ -309,7 +309,7 @@ Scenario: Edit Sequence Relationship And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" When I press "bim.assign_successor(task={nested_task_two})" And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" And I press "bim.enable_editing_sequence_attributes(sequence={rel_sequence})" @@ -340,7 +340,7 @@ Scenario: Animate the construction of a wall And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "CONSTRUCTION" And I press "bim.edit_task" And I press "bim.enable_editing_task_time(task={task})" @@ -376,7 +376,7 @@ Scenario: Animate the demolition of a wall And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "DEMOLITION" And I press "bim.edit_task" And I press "bim.enable_editing_task_time(task={task})" @@ -415,7 +415,7 @@ Scenario: Animate the operation of a wall And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "OPERATION" And I press "bim.edit_task" And I press "bim.enable_editing_task_time(task={task})" @@ -448,7 +448,7 @@ Scenario: Animate the movement of a wall And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "MOVE" And I press "bim.edit_task" And I press "bim.enable_editing_task_time(task={task})" @@ -539,7 +539,7 @@ Scenario: Generate Gantt Chart And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" - And I press "bim.enable_editing_task(task={task})" + And I press "bim.enable_editing_task_attributes(task={task})" And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "CONSTRUCTION" And I press "bim.edit_task" And I press "bim.enable_editing_task_time(task={task})" @@ -788,7 +788,7 @@ Scenario: Duplicate Task And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" And I press "bim.assign_successor(task={nested_task_two})" When I press "bim.duplicate_task(task={task})" Then nothing happens @@ -804,7 +804,7 @@ Scenario: Duplicate Task and edit sequence Relationship And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" When I press "bim.add_task(task={task})" And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" - And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.enable_editing_task_attributes(task={nested_task_one})" And I press "bim.assign_successor(task={nested_task_two})" And I press "bim.duplicate_task(task={task})" When I press "bim.enable_editing_task_sequence(task={nested_task_one})" diff --git a/src/ifcopenshell-python/ifcopenshell/util/resource.py b/src/ifcopenshell-python/ifcopenshell/util/resource.py new file mode 100644 index 0000000000..d3d118c5f5 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/resource.py @@ -0,0 +1,102 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.util.element +import ifcopenshell.util.date + + +def get_productivity(resource, should_inherit=True): + productivity = ifcopenshell.util.element.get_psets( + resource + ).get("EPset_Productivity", None) + if should_inherit and not productivity: + # Proposal for Schema - If instance doesn't have any productivity, inherit it's parent's productivity + if not resource.Nests: + return None + else: + parent_resource = resource.Nests[0].RelatingObject + productivity = ifcopenshell.util.element.get_psets( + parent_resource + ).get("EPset_Productivity", None) + return productivity + +def get_unit_consumed(productivity): + duration = productivity.get("BaseQuantityConsumed", None) + if not duration: + return + return ifcopenshell.util.date.ifc2datetime(duration) + +def get_quantity_produced(productivity): + if not productivity: + return 0 + return productivity.get("BaseQuantityProducedValue", 0) + +def get_quantity_produced_name(productivity): + if not productivity: + return "" + return productivity.get("BaseQuantityProducedName", "") + +def get_total_quantity_produced(resource, quantity_name_in_process): + def get_product_quantity(product, quantity_name): + psets = ifcopenshell.util.element.get_psets(product) + for pset in psets.values(): + for name, value in pset.items(): + if name == quantity_name: + return float(value) + + total = 0 + products = get_parametric_resource_products(resource) + if quantity_name_in_process == "Count": + total = len(products) + else: + for product in products: + total += get_product_quantity(product, quantity_name_in_process) + return total + +def get_parametric_resource_products(resource): + products = [] + for rel in resource.HasAssignments or []: + if not rel.is_a("IfcRelAssignsToProcess"): + continue + for rel2 in rel.RelatingProcess.HasAssignments or []: + if not rel2.is_a("IfcRelAssignsToProduct"): + continue + products.append(rel2.RelatingProduct) + return products + +def get_resource_required_work(resource): + productivity = get_productivity(resource) + if productivity: + quantity_produced = get_quantity_produced(productivity) + time_consumed = get_unit_consumed(productivity) + quantity_name_in_process = get_quantity_produced_name(productivity) + total_quantity_to_produce = get_total_quantity_produced(resource, quantity_name_in_process) + if not time_consumed or not quantity_produced or not total_quantity_to_produce: + return + iso_string = "" + if "T" in productivity.get("BaseQuantityConsumed", ""): + seconds = (time_consumed.days * 24 * 60 * 60) + time_consumed.seconds + productivity_ratio = seconds / quantity_produced + required_work = total_quantity_to_produce * productivity_ratio + iso_string = f"PT{required_work / 60 / 60}H" + else: + days = time_consumed.days + (time_consumed.seconds / (24 * 60 * 60)) + productivity_ratio = days / quantity_produced + required_work = total_quantity_to_produce * productivity_ratio + iso_string = f"P{required_work}D" + return iso_string \ No newline at end of file