diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index d4e26a310c..8f6fbe4d6c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -117,6 +117,7 @@ classes = ( operator.DisableEditingTaskAnimationColors, operator.LoadProductTasks, operator.HighlightTask, + operator.CreateBaseline, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/data.py b/src/blenderbim/blenderbim/bim/module/sequence/data.py index 457cdff139..123e51977c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/data.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/data.py @@ -20,12 +20,14 @@ import bpy import blenderbim.tool as tool import ifcopenshell from ifcopenshell.util.doc import get_predefined_type_doc +import ifcopenshell.util.date as dateutil def refresh(): SequenceData.is_loaded = False WorkPlansData.is_loaded = False TaskICOMData.is_loaded = False + WorkScheduleData.is_loaded = False class SequenceData: @@ -245,6 +247,45 @@ class SequenceData: return results +class WorkScheduleData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "can_have_baselines": cls.can_have_baselines(), + "active_work_schedule_baselines": cls.active_work_schedule_baselines(), + } + cls.is_loaded = True + + @classmethod + def can_have_baselines(cls): + if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + return False + return ( + tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id).PredefinedType + == "PLANNED" + ) + + @classmethod + def active_work_schedule_baselines(cls): + results = [] + if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + return [] + for rel in tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id).Declares: + for work_schedule in rel.RelatedObjects: + if work_schedule.PredefinedType == "BASELINE": + results.append( + { + "id": work_schedule.id(), + "name": work_schedule.Name or "Unnamed", + "date": str(dateutil.ifc2datetime(work_schedule.CreationDate)), + } + ) + return results + + class WorkPlansData: data = {} is_loaded = False diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 21f113fa68..735430b403 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -168,15 +168,14 @@ class EnableEditingWorkSchedule(bpy.types.Operator): return {"FINISHED"} -class EnableEditingWorkScheduleTasks(bpy.types.Operator): +class EnableEditingWorkScheduleTasks(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_work_schedule_tasks" bl_label = "Enable Editing Work Schedule Tasks" bl_options = {"REGISTER", "UNDO"} work_schedule: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): core.enable_editing_work_schedule_tasks(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) - return {"FINISHED"} class LoadTaskProperties(bpy.types.Operator): @@ -1370,3 +1369,23 @@ class ReorderTask(bpy.types.Operator, tool.Ifc.Operator): ) if isinstance(r, str): self.report({"WARNING"}, r) + + +class CreateBaseline(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.create_baseline" + bl_label = "Create Schedule Baseline" + bl_options = {"REGISTER", "UNDO"} + work_schedule: bpy.props.IntProperty() + name: bpy.props.StringProperty() + + def _execute(self, context): + core.create_baseline( + tool.Ifc, tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule), name=self.name + ) + + def draw(self, context): + layout = self.layout + layout.prop(self, "name", text="Baseline Name") + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 6fd241730d..2e146a596d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -425,6 +425,7 @@ class BIMWorkScheduleProperties(PropertyGroup): 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) + should_show_schedule_baseline_ui: BoolProperty(name="Baselines", default=False) class BIMTaskTreeProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 07fc718680..d238fd8bb2 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -21,7 +21,7 @@ import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes -from blenderbim.bim.module.sequence.data import WorkPlansData, SequenceData, TaskICOMData +from blenderbim.bim.module.sequence.data import WorkPlansData, WorkScheduleData, SequenceData, TaskICOMData class BIM_PT_work_plans(Panel): @@ -113,6 +113,8 @@ class BIM_PT_work_schedules(Panel): def draw(self, context): if not SequenceData.is_loaded: SequenceData.load() + if not WorkScheduleData.is_loaded: + WorkScheduleData.load() self.props = context.scene.BIMWorkScheduleProperties self.tprops = context.scene.BIMTaskTreeProperties self.animation_props = context.scene.BIMAnimationProperties @@ -132,68 +134,80 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.add_work_schedule", text="Add new", icon="ADD") for work_schedule_id, work_schedule in SequenceData.data["work_schedules"].items(): + self.draw_work_schedule_ui(work_schedule_id, work_schedule) def draw_work_schedule_ui(self, work_schedule_id, work_schedule): - row = self.layout.row(align=True) - row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON") - if self.props.active_work_schedule_id == work_schedule_id: - if self.props.editing_type == "WORK_SCHEDULE": - row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK") - elif self.props.editing_type == "TASKS": - grid = self.layout.grid_flow(columns=2, even_columns=True) + if not work_schedule["PredefinedType"] == "BASELINE": + row = self.layout.row(align=True) + row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON") + if self.props.active_work_schedule_id == work_schedule_id: + if self.props.editing_type == "WORK_SCHEDULE": + row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK") + elif self.props.editing_type == "TASKS": + grid = self.layout.grid_flow(columns=2, even_columns=True) - col = grid.column() - row1 = col.row(align=True) - row1.alignment = "LEFT" - row1.label(text="Schedule tools") - row1 = col.row(align=True) - row1.alignment = "RIGHT" - row1.operator( - "bim.generate_gantt_chart", text="Generate Gantt", icon="NLA" + col = grid.column() + row1 = col.row(align=True) + row1.alignment = "LEFT" + row1.label(text="Schedule tools") + row1 = col.row(align=True) + row1.alignment = "RIGHT" + row1.operator( + "bim.generate_gantt_chart", text="Generate Gantt", icon="NLA" + ).work_schedule = work_schedule_id + row1.operator( + "bim.recalculate_schedule", text="Re-calculate Schedule", icon="FILE_REFRESH" + ).work_schedule = work_schedule_id + row2 = col.row(align=True) + row2.alignment = "RIGHT" + row2.operator( + "bim.select_work_schedule_products", text="Select Assigned", icon="RESTRICT_SELECT_OFF" + ).work_schedule = work_schedule_id + row2.operator( + "bim.select_unassigned_work_schedule_products", + text="Select Unassigned", + icon="RESTRICT_SELECT_OFF", + ).work_schedule = work_schedule_id + if WorkScheduleData.data["can_have_baselines"]: + row3 = col.row() + row3.alignment = "RIGHT" + row3.prop(self.props, "should_show_schedule_baseline_ui", icon="RESTRICT_INSTANCED_OFF") + col = grid.column() + row1 = col.row(align=True) + row1.alignment = "LEFT" + row1.label(text="Settings") + row1 = col.row(align=True) + row1.alignment = "RIGHT" + row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY") + 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( + "bim.enable_editing_work_schedule_tasks", text="", icon="ACTION" ).work_schedule = work_schedule_id - row1.operator( - "bim.recalculate_schedule", text="Re-calculate Schedule", icon="FILE_REFRESH" + row.operator( + "bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL" ).work_schedule = work_schedule_id - row2 = col.row(align=True) - row2.alignment = "RIGHT" - row2.operator( - "bim.select_work_schedule_products", text="Select Assigned", icon="RESTRICT_SELECT_OFF" - ).work_schedule = work_schedule_id - row2.operator( - "bim.select_unassigned_work_schedule_products", text="Select Unassigned", icon="RESTRICT_SELECT_OFF" - ).work_schedule = work_schedule_id - col = grid.column() - row1 = col.row(align=True) - row1.alignment = "LEFT" - row1.label(text="Settings") - row1 = col.row(align=True) - row1.alignment = "RIGHT" - row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY") - 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( - "bim.enable_editing_work_schedule_tasks", text="", icon="ACTION" - ).work_schedule = work_schedule_id - row.operator( - "bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL" - ).work_schedule = work_schedule_id - row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id + row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id - if self.props.active_work_schedule_id == work_schedule_id: - if self.props.editing_type == "WORK_SCHEDULE": - self.draw_editable_work_schedule_ui() - elif self.props.editing_type == "TASKS": - if self.props.should_show_column_ui: + if self.props.active_work_schedule_id == work_schedule_id: + if self.props.editing_type == "WORK_SCHEDULE": + self.draw_editable_work_schedule_ui() + elif self.props.editing_type == "TASKS": + self.draw_baseline_ui(work_schedule_id) 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) + 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) + else: + self.draw_readonly_work_schedule_ui(work_schedule_id) def draw_task_operators(self): row = self.layout.row(align=True) @@ -229,6 +243,8 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.remove_task", text="Delete", icon="X").task = ifc_definition_id def draw_column_ui(self): + if not self.props.should_show_column_ui: + return row = self.layout.row() row.operator("bim.setup_default_task_columns", text="Add Default Columns", icon="ANCHOR_BOTTOM") row.alignment = "RIGHT" @@ -456,6 +472,48 @@ class BIM_PT_work_schedules(Panel): def draw_editable_task_time_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.task_time_attributes, self.layout) + def draw_baseline_ui(self, work_schedule_id): + if not self.props.should_show_schedule_baseline_ui: + return + row3 = self.layout.row() + row3.alignment = "RIGHT" + row3.operator("bim.create_baseline", text="Add Baseline", icon="ADD").work_schedule = work_schedule_id + if WorkScheduleData.data["active_work_schedule_baselines"]: + for baseline in WorkScheduleData.data["active_work_schedule_baselines"]: + baseline_row = self.layout.row() + baseline_row.alignment = "RIGHT" + baseline_row.label( + text="{} @ {}".format(baseline["name"], baseline["date"]), icon="RESTRICT_INSTANCED_OFF" + ) + baseline_row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = baseline["id"] + baseline_row.operator( + "bim.enable_editing_work_schedule_tasks", text="", icon="ACTION" + ).work_schedule = baseline["id"] + + def draw_readonly_work_schedule_ui(self, work_schedule_id): + if self.props.active_work_schedule_id == work_schedule_id: + row = self.layout.row() + row.alignment = "RIGHT" + row.operator("bim.disable_editing_work_schedule", text="Disable editing", icon="CANCEL") + grid = self.layout.grid_flow(columns=2, even_columns=True) + col = grid.column() + row1 = col.row(align=True) + row1.alignment = "LEFT" + row1.label(text="Settings") + row1 = col.row(align=True) + row1.alignment = "RIGHT" + row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY") + row2 = col.row(align=True) + if self.props.editing_type == "TASKS": + self.draw_column_ui() + self.layout.template_list( + "BIM_UL_tasks", + "", + self.tprops, + "tasks", + self.props, + "active_task_index", + ) class BIM_PT_task_icom(Panel): bl_label = "IFC Task ICOM" diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index 3f1a884867..d400a6bedd 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -143,6 +143,7 @@ def add_task(ifc, sequence, parent_task=None): def enable_editing_task(sequence, task=None): sequence.enable_editing_task(task) + def enable_editing_task_attributes(sequence, task=None): sequence.load_task_attributes(task) sequence.enable_editing_task_attributes(task) @@ -248,7 +249,10 @@ def unassign_input_products(ifc, sequence, spatial, task=None, products=None): def assign_resource(ifc, sequence, task=None): resource = sequence.get_selected_resource() sub_resource = ifc.run( - "resource.add_resource", parent_resource=resource, ifc_class=resource.is_a(), name=resource.Name + "resource.add_resource", + parent_resource=resource, + ifc_class=resource.is_a(), + name="{}/{}".format(resource.Name or "Unnamed", task.Name or ""), ) ifc.run("sequence.assign_process", relating_process=task, related_object=sub_resource) resources = sequence.get_task_resources(task) @@ -552,7 +556,11 @@ def reorder_task_nesting(ifc, sequence, task, new_index): 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() + 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 + sequence.load_task_properties() + + +def create_baseline(ifc, sequence, work_schedule, name): + ifc.run("sequence.create_baseline", work_schedule=work_schedule, name=name) diff --git a/src/blenderbim/blenderbim/tool/resource.py b/src/blenderbim/blenderbim/tool/resource.py index b14556580a..7883440c60 100644 --- a/src/blenderbim/blenderbim/tool/resource.py +++ b/src/blenderbim/blenderbim/tool/resource.py @@ -336,7 +336,6 @@ class Resource(blenderbim.core.tool.Resource): 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)