diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 2904ae9814..16af753c26 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -4,8 +4,6 @@ from . import ui, prop, operator classes = ( operator.LoadWorkPlans, operator.DisableWorkPlanEditingUI, - operator.LoadTasks, - operator.DisableTaskEditingUI, operator.AddWorkPlan, operator.EditWorkPlan, operator.RemoveWorkPlan, @@ -18,28 +16,41 @@ classes = ( operator.RemoveWorkSchedule, operator.EnableEditingWorkSchedule, operator.DisableEditingWorkSchedule, + operator.LoadTasks, + operator.DisableTaskEditingUI, + operator.LoadWorkCalendars, + operator.DisableWorkCalendarEditingUI, + operator.AddWorkCalendar, + operator.EditWorkCalendar, + operator.RemoveWorkCalendar, + operator.EnableEditingWorkCalendar, + operator.DisableEditingWorkCalendar, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.WorkSchedule, prop.BIMWorkScheduleProperties, + prop.WorkCalendar, + prop.BIMWorkCalendarProperties, prop.Task, prop.BIMTaskProperties, ui.BIM_PT_work_plans, ui.BIM_UL_work_plans, ui.BIM_PT_work_schedules, ui.BIM_UL_work_schedules, + ui.BIM_PT_work_calendars, + ui.BIM_UL_work_calendars, ui.BIM_PT_tasks, ui.BIM_UL_tasks, ) - def register(): bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties) bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties) bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties) - + bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties) def unregister(): del bpy.types.Scene.BIMTaskProperties del bpy.types.Scene.BIMWorkPlanProperties del bpy.types.Scene.BIMWorkScheduleProperties + del bpy.types.Scene.BIMWorkCalendarProperties diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 163fa8423b..6db448792d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -226,6 +226,118 @@ class DisableEditingWorkSchedule(bpy.types.Operator): def execute(self, context): context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 return {"FINISHED"} + + +class LoadWorkCalendars(bpy.types.Operator): + bl_idname = "bim.load_work_calendars" + bl_label = "Load Work Calendars" + + def execute(self, context): + props = context.scene.BIMWorkCalendarProperties + while len(props.work_calendars) > 0: + props.work_calendars.remove(0) + for ifc_definition_id, work_calendar in Data.work_calendars.items(): + new = props.work_calendars.add() + new.ifc_definition_id = ifc_definition_id or "Unnamed" + new.name = work_calendar["Name"] + props.is_editing = True + bpy.ops.bim.disable_editing_work_calendar() + return {"FINISHED"} + +class DisableWorkCalendarEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_work_calendar_editing_ui" + bl_label = "Disable WorkCalendar Editing UI" + + def execute(self, context): + context.scene.BIMWorkCalendarProperties.is_editing = False + return {"FINISHED"} + + +class AddWorkCalendar(bpy.types.Operator): + bl_idname = "bim.add_work_calendar" + bl_label = "Add Work Calendar" + + def execute(self, context): + result = ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file()) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_calendars() + return {"FINISHED"} + + +class EditWorkCalendar(bpy.types.Operator): + bl_idname = "bim.edit_work_calendar" + bl_label = "Edit Work Calendar" + + def execute(self, context): + props = context.scene.BIMWorkCalendarProperties + attributes = {} + for attribute in props.work_calendar_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 == "enum": + attributes[attribute.name] = attribute.enum_value + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.edit_work_calendar", self.file, **{ + "work_calendar": self.file.by_id(props.active_work_calendar_id), + "attributes": attributes + }) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_calendars() + return {"FINISHED"} + +class RemoveWorkCalendar(bpy.types.Operator): + bl_idname = "bim.remove_work_calendar" + bl_label = "Remove Work Plan" + work_calendar: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkCalendarProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.remove_work_calendar", self.file, **{"work_calendar": self.file.by_id(self.work_calendar)}) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_calendars() + return {"FINISHED"} + +class EnableEditingWorkCalendar(bpy.types.Operator): + bl_idname = "bim.enable_editing_work_calendar" + bl_label = "Enable Editing Work Plan" + work_calendar: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkCalendarProperties + while len(props.work_calendar_attributes) > 0: + props.work_calendar_attributes.remove(0) + + data = Data.work_calendars[self.work_calendar] + + for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkCalendar").all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = props.work_calendar_attributes.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + 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()] + 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_work_calendar_id = self.work_calendar + return {"FINISHED"} + + +class DisableEditingWorkCalendar(bpy.types.Operator): + bl_idname = "bim.disable_editing_work_calendar" + bl_label = "Disable Editing Work Calendar" + + def execute(self, context): + context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0 return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index efe499c15f..10812f3ac1 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -48,4 +48,17 @@ class BIMWorkScheduleProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) work_schedules: CollectionProperty(name="Work Schedules", type=WorkSchedule) active_work_schedule_index: IntProperty(name="Active Work Schedules Index") - active_work_schedule_id: IntProperty(name="Active Work Schedules Id") \ No newline at end of file + active_work_schedule_id: IntProperty(name="Active Work Schedules Id") + + +class WorkCalendar(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMWorkCalendarProperties(PropertyGroup): + work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute) + is_editing: BoolProperty(name="Is Editing", default=False) + work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar) + active_work_calendar_index: IntProperty(name="Active Work Calendar Index") + active_work_calendar_id: IntProperty(name="Active Work Calendar Id") \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index e69194dd12..8eaaa8fcce 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -122,10 +122,65 @@ class BIM_UL_work_schedules(UIList): op = row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL") op.work_schedule = item.ifc_definition_id row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id + +class BIM_PT_work_calendars(Panel): + bl_label = "IFC Work Calendars" + bl_idname = "BIM_PT_work_calendars" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + if not Data.is_loaded: + Data.load(IfcStore.get_file()) + self.props = context.scene.BIMWorkCalendarProperties + row = self.layout.row(align=True) + row.label(text="{} Work Calendar Found".format(len(Data.work_calendars)), icon="TEXT") + if self.props.is_editing: + row.operator("bim.add_work_calendar", text="", icon="ADD") + row.operator("bim.disable_work_calendar_editing_ui", text="", icon="CHECKMARK") + else: + row.operator("bim.load_work_calendars", text="", icon="GREASEPENCIL") + + if self.props.is_editing: + self.layout.template_list( + "BIM_UL_work_calendars", + "", + self.props, + "work_calendars", + self.props, + "active_work_calendar_index", + ) + + if self.props.active_work_calendar_id: + self.draw_editable_ui(context) + + def draw_editable_ui(self, context): + for attribute in self.props.work_calendar_attributes: row = self.layout.row(align=True) - row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT") - row.operator("bim.add_work_plan", text="", icon="GREASEPENCIL") - row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id + row.prop(attribute, "string_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_work_calendars(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + if context.scene.BIMWorkCalendarProperties.active_work_calendar_id == item.ifc_definition_id: + row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_work_calendar", text="", icon="X") + elif context.scene.BIMWorkCalendarProperties.active_work_calendar_id: + row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id + else: + 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_PT_tasks(Panel): @@ -175,4 +230,4 @@ class BIM_UL_tasks(UIList): row = layout.row(align=True) if item.identification: layout.label(text=item.identification) - layout.label(text=item.name) + layout.label(text=item.name) \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py new file mode 100644 index 0000000000..f8526c0e37 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py @@ -0,0 +1,48 @@ +import ifcopenshell.api +import ifcopenshell.util.date +from datetime import datetime, timedelta + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "name": "Unnamed", + "predefined_type": "NOTDEFINED", + "working_times":[], + "exception_times":[] + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + work_calendar = ifcopenshell.api.run( + "root.create_entity", + self.file, + ifc_class="IfcWorkCalendar", + predefined_type=self.settings["predefined_type"], + name=self.settings["name"], + ) + working_time = self.file.create_entity("IfcWorkTime", **{"Name":"DefaultWorkingTime"}) + self.settings["working_times"].append(working_time) + work_calendar.WorkingTimes = self.settings["working_times"] + + exception_time = self.file.create_entity("IfcWorkTime", **{"Name":"DefaultExceptionTime"}) + self.settings["exception_times"].append(exception_time) + work_calendar.ExceptionTimes = self.settings["exception_times"] + + context = self.file.by_type("IfcContext")[0] + if context.Declares: + rel_declares = context.Declares[0] + ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel_declares) + else: + rel_declares = self.file.create_entity("IfcRelDeclares", **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatingContext": context + }) + related_definitions = list(rel_declares.RelatedDefinitions or []) + related_definitions.append(work_calendar) + rel_declares.RelatedDefinitions = related_definitions + + return work_calendar \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar_times.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar_times.py new file mode 100644 index 0000000000..307ae44256 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar_times.py @@ -0,0 +1,37 @@ +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.util.date +from datetime import datetime, timedelta + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "object": None, + "name":None, + "recurrence_pattern": None, + "start": datetime.now(), + "finish": datetime.now() + timedelta(days=365) + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + working_time = self.file.create_entity("IfcWorkTime", **{ + "Name": self.settings["name"], + "Start": ifcopenshell.util.date.datetime2ifc(self.settings["start"], "IfcTime"), + "Finish": ifcopenshell.util.date.datetime2ifc(self.settings["finish"], "IfcTime") + }) + + #TODO Implement user settings for recurrence pattern + working_time.RecurrencePattern = ifcopenshell.api.run("time.add_recurrence_pattern", self.file) + + if self.settings["object"].is_a('IfcWorkCalendar'): + if self.settings["object"].WorkingTimes: + working_times = list(self.settings["object"].WorkingTimes) + working_times.append(working_time) + self.settings["object"].WorkingTimes = working_times + else: + self.settings["object"].WorkingTimes = [working_time] + return working_time \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 7253f29625..523e06ed88 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -1,15 +1,17 @@ import ifcopenshell.util.date - class Data: is_loaded = False work_plans = {} + work_schedules = {} tasks = {} @classmethod def purge(cls): cls.is_loaded = False cls.work_plans = {} + cls.work_schedules = {} + cls.work_calendars = {} cls.tasks = {} @classmethod @@ -19,6 +21,7 @@ class Data: return cls.load_work_plans() cls.load_work_schedules() + cls.load_work_calendars() cls.load_tasks() cls.is_loaded=True @@ -36,6 +39,45 @@ class Data: data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]).isoformat() cls.work_plans[work_plan.id()] = data + @classmethod + def load_work_schedules(cls): + cls.work_schedules = {} + for work_schedule in cls._file.by_type("IfcWorkSchedule"): + data = work_schedule.get_info() + del data["OwnerHistory"] + if data["Creators"]: + data["Creators"] = [p.id() for p in data["Creators"]] + data["CreationDate"] = ifcopenshell.util.date.ifc2datetime(data["CreationDate"]).isoformat() + data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"]).isoformat() + if data["FinishTime"]: + data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]).isoformat() + cls.work_schedules[work_schedule.id()] = data + + @classmethod + def load_work_calendars(cls): + for work_calendar in cls._file.by_type("IfcWorkCalendar"): + data = work_calendar.get_info() + del data["OwnerHistory"] + ##### HOW DO WE NEST ENTITIES SUCH AS WORKTIME? #### + ##### HOW TO ALLOW DISPLAY OF TIME? AND DATES? #### + del data["WorkingTimes"] + del data["ExceptionTimes"] + # if data["WorkingTimes"]: + # for worktime in data["WorkingTimes"]: + # worktime_data = {} + # if worktime.Start: + # worktime_data["start"] = ifcopenshell.util.date.ifc2datetime(worktime.Start).isoformat() + # if worktime.Start: + # worktime_data["finish"] = ifcopenshell.util.date.ifc2datetime(worktime.Start).isoformat() + # worktime = worktime_data + # if data["ExceptionTimes"]: + # for exceptiontime in data["ExceptionTimes"]: + # exceptiontime_data = {} + # exceptiontime_data["start"] = ifcopenshell.util.date.ifc2datetime(exceptiontime.Finish).isoformat() + # exceptiontime_data["finish"] = ifcopenshell.util.date.ifc2datetime(exceptiontime.Finish).isoformat() + # exceptiontime = exceptiontime_data + cls.work_calendars[work_calendar.id()] = data + @classmethod def load_tasks(cls): cls.tasks = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py new file mode 100644 index 0000000000..0516508733 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "work_calendar": 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["work_calendar"], name, value) \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py new file mode 100644 index 0000000000..549509f050 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py @@ -0,0 +1,14 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"work_calendar": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + # TODO: do a deep purge + if self.settings["work_calendar"].HasContext: + rel_declares = self.settings["work_calendar"].HasContext[0] + if len(rel_declares.RelatedDefinitions) == 1: + self.file.remove(rel_declares) + self.file.remove(self.settings["work_calendar"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/time/add_recurrence_pattern.py b/src/ifcopenshell-python/ifcopenshell/api/time/add_recurrence_pattern.py new file mode 100644 index 0000000000..8e890a443b --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/time/add_recurrence_pattern.py @@ -0,0 +1,21 @@ +import ifcopenshell +import ifcopenshell.api +from datetime import time + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"recurrence_type": 'WEEKLY'} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + + recurrence = self.file.createIfcRecurrencePattern(self.settings['recurrence_type']) + #recurrence.WeekdayComponent = [self.file.createIfcDayInWeekNumber(1),self.file.createIfcDayInWeekNumber(2)] + recurrence.TimePeriods = [ + self.file.createIfcTimePeriod(time(8).isoformat(),time(12).isoformat()), + self.file.createIfcTimePeriod(time(13).isoformat(),time(17).isoformat()) + ] + return recurrence \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/time/add_work_time.py b/src/ifcopenshell-python/ifcopenshell/api/time/add_work_time.py new file mode 100644 index 0000000000..e69de29bb2