diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index c588617c4d..6ba11ff1e0 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -36,9 +36,9 @@ classes = ( operator.AssignSuccessor, operator.UnassignPredecessor, operator.UnassignSuccessor, - operator.AddTaskTime, operator.EnableEditingTaskTime, operator.DisableEditingTaskTime, + operator.EditTaskTime, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 091da38c15..5ebffd575c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1,6 +1,8 @@ import bpy import json import ifcopenshell.api +from datetime import datetime +from dateutil.parser import parse from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.sequence.data import Data @@ -232,6 +234,16 @@ class EnableEditingTasks(bpy.types.Operator): new.ifc_definition_id = related_object_id new.name = task["Name"] or "Unnamed" new.identification = task["Identification"] or "X" + if task["TaskTime"]: + task_time = Data.task_times[task["TaskTime"]] + new.start = self.canonicalise_time(task_time["ScheduleStart"]) + new.finish = self.canonicalise_time(task_time["ScheduleFinish"]) + # TODO: duration + new.duration = "-" + else: + new.start = "-" + new.finish = "-" + new.duration = "-" new.is_expanded = related_object_id not in self.contracted_tasks new.level_index = level_index if task["RelatedObjects"]: @@ -240,6 +252,11 @@ class EnableEditingTasks(bpy.types.Operator): for related_object_id in task["RelatedObjects"]: self.create_new_task_li(related_object_id, level_index + 1) + def canonicalise_time(self, time): + if not time: + return "-" + return time.strftime("%d/%m/%y") + class DisableEditingWorkSchedule(bpy.types.Operator): bl_idname = "bim.disable_editing_work_schedule" @@ -481,16 +498,12 @@ class EnableEditingTaskTime(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties self.file = IfcStore.get_file() + + task_time_id = Data.tasks[self.task]["TaskTime"] or self.add_task_time().id() + while len(props.task_time_attributes) > 0: props.task_time_attributes.remove(0) - if self.file.by_id(self.task).TaskTime: - task_time_id = self.file.by_id(self.task).TaskTime.id() - else: - task_time = ifcopenshell.api.run("sequence.add_task_time", self.file) - self.file.by_id(self.task).TaskTime = task_time - task_time_id = task_time.id() - Data.load(self.file) data = Data.task_times[task_time_id] for attribute in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes(): @@ -503,18 +516,82 @@ class EnableEditingTaskTime(bpy.types.Operator): new.is_optional = attribute.optional() new.data_type = data_type if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] + if isinstance(data[attribute.name()], datetime): + new.string_value = "" if new.is_null else data[attribute.name()].isoformat() + else: + new.string_value = "" if new.is_null else data[attribute.name()] elif data_type == "boolean": new.bool_value = False if new.is_null else data[attribute.name()] - elif data_type == "integer": - new.int_value = 0 if new.is_null else data[attribute.name()] + elif data_type == "float": + new.float_value = 0.0 if new.is_null else data[attribute.name()] elif data_type == "enum": new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) if data[attribute.name()]: new.enum_value = data[attribute.name()] - props.active_task_time_id = IfcStore.get_file().by_id(self.task).TaskTime.id() + props.active_task_time_id = task_time_id + props.active_task_id = self.task return {"FINISHED"} + def add_task_time(self): + task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.file.by_id(self.task)) + Data.load(IfcStore.get_file()) + return task_time + + +class DisableEditingTaskTime(bpy.types.Operator): + bl_idname = "bim.disable_editing_task_time" + bl_label = "Disable Editing Task Time" + + def execute(self, context): + context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + bpy.ops.bim.disable_editing_task() + return {"FINISHED"} + + +class EditTaskTime(bpy.types.Operator): + bl_idname = "bim.edit_task_time" + bl_label = "Edit Task Time" + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + attributes = {} + for attribute in props.task_time_attributes: + if attribute.is_null: + attributes[attribute.name] = None + else: + if attribute.data_type == "string": + attributes[attribute.name] = attribute.string_value + elif attribute.data_type == "boolean": + attributes[attribute.name] = attribute.bool_value + elif attribute.data_type == "float": + attributes[attribute.name] = attribute.float_value + elif attribute.data_type == "enum": + attributes[attribute.name] = attribute.enum_value + + attributes = self.convert_strings_to_date_times(attributes) + + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "sequence.edit_task_time", + self.file, + **{"task_time": self.file.by_id(props.active_task_time_id), "attributes": attributes} + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.disable_editing_task_time() + bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) + return {"FINISHED"} + + def convert_strings_to_date_times(self, attributes): + for key, value in attributes.items(): + if not value: + continue + if "Start" in key or "Finish" in key or key == "StatusTime": + try: + attributes[key] = parse(value) + except: + attributes[key] = None + return attributes + class EnableEditingTask(bpy.types.Operator): bl_idname = "bim.enable_editing_task" @@ -551,15 +628,6 @@ class EnableEditingTask(bpy.types.Operator): return {"FINISHED"} -class DisableEditingTaskTime(bpy.types.Operator): - bl_idname = "bim.disable_editing_task_time" - bl_label = "Disable Editing Task Time" - - def execute(self, context): - context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 - return {"FINISHED"} - - class DisableEditingTask(bpy.types.Operator): bl_idname = "bim.disable_editing_task" bl_label = "Disable Editing Task" @@ -670,20 +738,3 @@ class UnassignSuccessor(bpy.types.Operator): ) Data.load(self.file) return {"FINISHED"} - - -class AddTaskTime(bpy.types.Operator): - bl_idname = "bim.add_task_time" - bl_label = "Add Task Time" - task: bpy.props.IntProperty() - - def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.add_task_time", - self.file, - task = self.file.by_id(self.task), - ) - Data.load(self.file) - return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index b3bf062ca5..d45798b9ac 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -3,6 +3,7 @@ import ifcopenshell.api from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.sequence.data import Data from blenderbim.bim.prop import StrProperty, Attribute +from dateutil.parser import parse from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -47,20 +48,53 @@ def updateTaskIdentification(self, context): attribute = context.scene.BIMWorkScheduleProperties.task_attributes.get("Identification") attribute.string_value = self.identification -def updateTaskTimeScheduleStart(self, context): - if self.schedule_start == "X": + +def updateTaskTimeStart(self, context): + updateTaskTimeDateTime(self, context, "start") + + +def updateTaskTimeFinish(self, context): + updateTaskTimeDateTime(self, context, "finish") + + +def updateTaskTimeDateTime(self, context, startfinish): + def canonicalise_time(time): + if not time: + return "-" + return time.strftime("%d/%m/%y") + + startfinish_key = "Schedule" + startfinish.capitalize() + startfinish_value = getattr(self, startfinish) + + if startfinish_value == "-": return self.file = IfcStore.get_file() props = context.scene.BIMWorkScheduleProperties + + try: + startfinish_datetime = parse(startfinish_value) + except: + setattr(self, startfinish, "-") + return + + task = self.file.by_id(self.ifc_definition_id) + if task.TaskTime: + task_time = task.TaskTime + else: + task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task) + Data.load(IfcStore.get_file()) + + if Data.task_times[task_time.id()][startfinish_key] == startfinish_datetime: + return + ifcopenshell.api.run( "sequence.edit_task_time", self.file, - **{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"ScheduleStart": self.schedule_start}} + **{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}} ) Data.load(IfcStore.get_file()) - if props.active_task_id == self.ifc_definition_id: - attribute = context.scene.BIMWorkScheduleProperties.task_attributes.get("ScheduleStart") - attribute.string_value = self.schedule_start + setattr(self, startfinish, canonicalise_time(startfinish_datetime)) + class Task(PropertyGroup): name: StringProperty(name="Name", update=updateTaskName) @@ -69,9 +103,10 @@ class Task(PropertyGroup): has_children: BoolProperty(name="Has Children") is_expanded: BoolProperty(name="Is Expanded") level_index: IntProperty(name="Level Index") - schedule_duration: StringProperty(name="Duration") - schedule_start: StringProperty(name="Schedule Start ", update=updateTaskTimeScheduleStart) - schedule_finish: StringProperty(name="Schedule Finish ") + duration: StringProperty(name="Duration") + start: StringProperty(name="Start", update=updateTaskTimeStart) + finish: StringProperty(name="Finish", update=updateTaskTimeFinish) + class WorkPlan(PropertyGroup): name: StringProperty(name="Name") diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index b3274bd4fc..5e2aa6c028 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -136,65 +136,39 @@ class BIM_PT_work_schedules(Panel): "active_task_index", ) if self.props.active_task_id: - for attribute in self.props.task_attributes: - row = self.layout.row(align=True) - if attribute.data_type == "string": - row.prop(attribute, "string_value", text=attribute.name) - elif attribute.data_type == "boolean": - row.prop(attribute, "bool_value", text=attribute.name) - elif attribute.data_type == "integer": - row.prop(attribute, "int_value", text=attribute.name) - elif attribute.data_type == "enum": - row.prop(attribute, "enum_value", text=attribute.name) - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + self.draw_editable_task_attributes_ui() + if self.props.active_task_time_id: + self.draw_editable_task_time_attributes_ui() + def draw_editable_task_attributes_ui(self): + for attribute in self.props.task_attributes: + row = self.layout.row(align=True) + if attribute.data_type == "string": + row.prop(attribute, "string_value", text=attribute.name) + elif attribute.data_type == "boolean": + row.prop(attribute, "bool_value", text=attribute.name) + elif attribute.data_type == "integer": + row.prop(attribute, "int_value", text=attribute.name) + elif attribute.data_type == "enum": + row.prop(attribute, "enum_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") -class BIM_UL_tasks(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - if item: - props = context.scene.BIMWorkScheduleProperties - row = layout.row(align=True) - for i in range(0, item.level_index): - row.label(text="", icon="BLANK1") - if item.has_children: - if item.is_expanded: - row.operator( - "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" - ).task = item.ifc_definition_id - else: - row.operator( - "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ).task = item.ifc_definition_id - else: - row.label(text="", icon="DOT") - row.prop(item, "identification", emboss=False, text="") - row.prop(item, "name", emboss=False, text="") - row.prop(item, "schedule_start", emboss=False, text="Start Time") - row.prop(item, "schedule_finish", emboss=False, text="Finish Time") - row.prop(item, "schedule_duration", emboss=False, text="Duration") - - if props.active_task_id == item.ifc_definition_id: - row.operator("bim.edit_task", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_task", text="", icon="CANCEL") - elif props.active_task_id: - if props.active_task_id in Data.tasks[item.ifc_definition_id]["IsPredecessorTo"]: - row.operator("bim.unassign_predecessor", text="", icon="BACK", emboss=False).task = item.ifc_definition_id - else: - row.operator("bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False).task = item.ifc_definition_id - - if props.active_task_id in Data.tasks[item.ifc_definition_id]["IsSuccessorFrom"]: - row.operator("bim.unassign_successor", text="", icon="FORWARD", emboss=False).task = item.ifc_definition_id - else: - row.operator("bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False).task = item.ifc_definition_id - - row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id - row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id - else: - row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id - row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id - row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id - row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id + def draw_editable_task_time_attributes_ui(self): + for attribute in self.props.task_time_attributes: + row = self.layout.row(align=True) + if attribute.data_type == "string": + row.prop(attribute, "string_value", text=attribute.name) + elif attribute.data_type == "boolean": + row.prop(attribute, "bool_value", text=attribute.name) + elif attribute.data_type == "integer": + row.prop(attribute, "int_value", text=attribute.name) + elif attribute.data_type == "float": + row.prop(attribute, "float_value", text=attribute.name) + elif attribute.data_type == "enum": + row.prop(attribute, "enum_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") class BIM_PT_work_calendars(Panel): @@ -259,3 +233,62 @@ class BIM_UL_work_calendars(UIList): op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL") op.work_calendar = item.ifc_definition_id row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id + + +class BIM_UL_tasks(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + props = context.scene.BIMWorkScheduleProperties + row = layout.row(align=True) + for i in range(0, item.level_index): + row.label(text="", icon="BLANK1") + if item.has_children: + if item.is_expanded: + row.operator( + "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" + ).task = item.ifc_definition_id + else: + row.operator( + "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" + ).task = item.ifc_definition_id + else: + row.label(text="", icon="DOT") + row.prop(item, "identification", emboss=False, text="") + row.prop(item, "name", emboss=False, text="") + + row.prop(item, "start", emboss=False, text="") + row.prop(item, "finish", emboss=False, text="") + row.prop(item, "duration", emboss=False, text="") + + if props.active_task_id == item.ifc_definition_id: + if props.active_task_time_id: + row.operator("bim.edit_task_time", text="", icon="CHECKMARK") + else: + row.operator("bim.edit_task", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_task", text="", icon="CANCEL") + elif props.active_task_id: + if props.active_task_id in Data.tasks[item.ifc_definition_id]["IsPredecessorTo"]: + row.operator( + "bim.unassign_predecessor", text="", icon="BACK", emboss=False + ).task = item.ifc_definition_id + else: + row.operator( + "bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False + ).task = item.ifc_definition_id + + if props.active_task_id in Data.tasks[item.ifc_definition_id]["IsSuccessorFrom"]: + row.operator( + "bim.unassign_successor", text="", icon="FORWARD", emboss=False + ).task = item.ifc_definition_id + else: + row.operator( + "bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False + ).task = item.ifc_definition_id + + row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id + row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id + else: + row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id + row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id + row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id + row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py index e37f60ebc4..a79eb12664 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py @@ -7,31 +7,11 @@ class Usecase: self.file = file self.settings = { "task": None, - "name": "Unnamed", - "duration_type": "NOTDEFINED", - "schedule_duration": None, - "schedule_start_time": datetime.now(), - "schedule_finish_time": datetime.now() + timedelta(days=5), } for key, value in settings.items(): self.settings[key] = value def execute(self): - task_time = self.file.create_entity("IfcTaskTime", **{"Name": self.settings["name"]}) - task_time.DurationType = self.settings["duration_type"] - task_time.ScheduleStart = ifcopenshell.util.date.datetime2ifc(self.settings["schedule_start_time"], "IfcDateTime") - duration = self.settings["schedule_duration"] - if duration: - task_time.ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration, "IfcTime") - task_time.ScheduleFinish = ifcopenshell.util.date.datetime2ifc( - self.settings["schedule_start"] + duration, - "IfcDateTime" - ) - else: - duration = self.settings["schedule_finish_time"] - self.settings["schedule_start_time"] - # task_time.ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration.days, "IfcTime") - task_time.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(self.settings["schedule_finish_time"], "IfcDateTime") - task = self.settings["task"] - if task: - task.TaskTime = task_time + task_time = self.file.create_entity("IfcTaskTime") + self.settings["task"].TaskTime = task_time return task_time diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 07f923b10a..b2289fc178 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -15,6 +15,7 @@ class Data: cls.work_schedules = {} cls.work_calendars = {} cls.tasks = {} + cls.task_times = {} @classmethod def load(cls, file): @@ -81,10 +82,7 @@ class Data: data["IsPredecessorTo"] = [] data["IsSuccessorFrom"] = [] if task.TaskTime: - data["TaskTime"] = task.TaskTime - data["ScheduleStart"] = task.TaskTime.ScheduleStart - data["ScheduleFinish"] = task.TaskTime.ScheduleFinish - data["ScheduleDuration"] = task.TaskTime.ScheduleDuration + data["TaskTime"] = data["TaskTime"].id() for rel in task.IsNestedBy: [data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcTask")] [data["IsPredecessorTo"].append(rel.RelatedProcess.id()) for rel in task.IsPredecessorTo or []] @@ -96,11 +94,10 @@ class Data: cls.task_times = {} for task_time in cls._file.by_type("IfcTaskTime"): data = task_time.get_info() - data["ScheduleStart"] = ifcopenshell.util.date.ifc2datetime(data["ScheduleStart"]) - data["ScheduleFinish"] = ifcopenshell.util.date.ifc2datetime(data["ScheduleFinish"]) - data["EarlyStart"] = ifcopenshell.util.date.ifc2datetime(data["EarlyStart"]) - data["EarlyFinish"] = ifcopenshell.util.date.ifc2datetime(data["EarlyFinish"]) - data["LateStart"] = ifcopenshell.util.date.ifc2datetime(data["LateStart"]) - data["LateFinish"] = ifcopenshell.util.date.ifc2datetime(data["LateFinish"]) - data["EarlyFinish"] = ifcopenshell.util.date.ifc2datetime(data["EarlyFinish"]) + for key, value in data.items(): + if not value: + continue + if "Start" in key or "Finish" in key or key == "StatusTime": + data[key] = ifcopenshell.util.date.ifc2datetime(value) + # TODO parse duration cls.task_times[task_time.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 140dff656e..5859e48221 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -1,10 +1,16 @@ +import ifcopenshell.util.date + + class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"task": None, "attributes": {}} + self.settings = {"task_time": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value def execute(self): for name, value in self.settings["attributes"].items(): - setattr(self.settings["task"].TaskTime, name, value) + if "Start" in name or "Finish" in name or name == "StatusTime": + if value: + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") + setattr(self.settings["task_time"], name, value)