diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 5eac12ed9c..2904ae9814 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -11,12 +11,23 @@ classes = ( operator.RemoveWorkPlan, operator.EnableEditingWorkPlan, operator.DisableEditingWorkPlan, + operator.LoadWorkSchedules, + operator.DisableWorkScheduleEditingUI, + operator.AddWorkSchedule, + operator.EditWorkSchedule, + operator.RemoveWorkSchedule, + operator.EnableEditingWorkSchedule, + operator.DisableEditingWorkSchedule, prop.WorkPlan, prop.BIMWorkPlanProperties, + prop.WorkSchedule, + prop.BIMWorkScheduleProperties, 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_tasks, ui.BIM_UL_tasks, ) @@ -25,8 +36,10 @@ classes = ( 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) def unregister(): del bpy.types.Scene.BIMTaskProperties del bpy.types.Scene.BIMWorkPlanProperties + del bpy.types.Scene.BIMWorkScheduleProperties diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 482e24fb01..163fa8423b 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -114,6 +114,118 @@ class DisableEditingWorkPlan(bpy.types.Operator): def execute(self, context): context.scene.BIMWorkPlanProperties.active_work_plan_id = 0 return {"FINISHED"} + +class LoadWorkSchedules(bpy.types.Operator): + bl_idname = "bim.load_work_schedules" + bl_label = "Load Work Schedules" + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + while len(props.work_schedules) > 0: + props.work_schedules.remove(0) + for ifc_definition_id, work_schedule in Data.work_schedules.items(): + new = props.work_schedules.add() + new.ifc_definition_id = ifc_definition_id or "Unnamed" + new.name = work_schedule["Name"] + props.is_editing = True + bpy.ops.bim.disable_editing_work_schedule() + return {"FINISHED"} + +class DisableWorkScheduleEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_work_schedule_editing_ui" + bl_label = "Disable WorkSchedule Editing UI" + + def execute(self, context): + context.scene.BIMWorkScheduleProperties.is_editing = False + return {"FINISHED"} + + +class AddWorkSchedule(bpy.types.Operator): + bl_idname = "bim.add_work_schedule" + bl_label = "Add Work Schedule" + + def execute(self, context): + result = ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file()) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_schedules() + return {"FINISHED"} + + +class EditWorkSchedule(bpy.types.Operator): + bl_idname = "bim.edit_work_schedule" + bl_label = "Edit Work Schedule" + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + attributes = {} + for attribute in props.work_schedule_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_schedule", self.file, **{ + "work_schedule": self.file.by_id(props.active_work_schedule_id), + "attributes": attributes + }) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_schedules() + return {"FINISHED"} + +class RemoveWorkSchedule(bpy.types.Operator): + bl_idname = "bim.remove_work_schedule" + bl_label = "Remove Work Schedule" + work_schedule: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.remove_work_schedule", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_schedules() + return {"FINISHED"} + +class EnableEditingWorkSchedule(bpy.types.Operator): + bl_idname = "bim.enable_editing_work_schedule" + bl_label = "Enable Editing Work Schedule" + work_schedule: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + while len(props.work_schedule_attributes) > 0: + props.work_schedule_attributes.remove(0) + + data = Data.work_schedules[self.work_schedule] + + for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = props.work_schedule_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_schedule_id = self.work_schedule + return {"FINISHED"} + + +class DisableEditingWorkSchedule(bpy.types.Operator): + bl_idname = "bim.disable_editing_work_schedule" + bl_label = "Disable Editing Work Schedule" + + def execute(self, context): + context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + return {"FINISHED"} return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index a1281d2ede..efe499c15f 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -35,4 +35,17 @@ class BIMWorkPlanProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) work_plans: CollectionProperty(name="Work Plans", type=WorkPlan) active_work_plan_index: IntProperty(name="Active Work Plan Index") - active_work_plan_id: IntProperty(name="Active Work Plan Id") \ No newline at end of file + active_work_plan_id: IntProperty(name="Active Work Plan Id") + + +class WorkSchedule(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMWorkScheduleProperties(PropertyGroup): + work_schedule_attributes: CollectionProperty(name="Work Schedule Attributes", type=Attribute) + 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 diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index e56c0a48c4..e69194dd12 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -62,6 +62,66 @@ class BIM_UL_work_plans(UIList): op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL") op.work_plan = item.ifc_definition_id row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id + + +class BIM_PT_work_schedules(Panel): + bl_label = "IFC Work Schedules" + bl_idname = "BIM_PT_work_schedules" + 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.BIMWorkScheduleProperties + row = self.layout.row(align=True) + row.label(text="{} Work Schedules Found".format(len(Data.work_schedules)), icon="TEXT") + if self.props.is_editing: + row.operator("bim.add_work_schedule", text="", icon="ADD") + row.operator("bim.disable_work_schedule_editing_ui", text="", icon="CHECKMARK") + else: + row.operator("bim.load_work_schedules", text="", icon="GREASEPENCIL") + + if self.props.is_editing: + self.layout.template_list( + "BIM_UL_work_schedules", + "", + self.props, + "work_schedules", + self.props, + "active_work_schedule_index", + ) + + if self.props.active_work_schedule_id: + self.draw_editable_ui(context) + + def draw_editable_ui(self, context): + for attribute in self.props.work_schedule_attributes: + row = self.layout.row(align=True) + 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_schedules(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.BIMWorkScheduleProperties.active_work_schedule_id == item.ifc_definition_id: + row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_work_schedule", text="", icon="X") + elif context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id + else: + 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 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") diff --git a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py index 805de997cc..30a8a85d95 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py @@ -50,3 +50,4 @@ class Usecase: "RelatingObject": self.settings["relating_object"], } ) + return is_decomposed_by diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py new file mode 100644 index 0000000000..cd7cb30ee0 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py @@ -0,0 +1,47 @@ +import ifcopenshell.api +import ifcopenshell.util.date +from datetime import datetime + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"name": "Unnamed", "predefined_type": "NOTDEFINED", "start_time": datetime.now(), "work_plan":None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + work_schedule = ifcopenshell.api.run( + "root.create_entity", + self.file, + ifc_class="IfcWorkSchedule", + predefined_type=self.settings["predefined_type"], + name=self.settings["name"], + ) + work_schedule.CreationDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime") + person = ifcopenshell.api.owner.settings.get_person(self.file) + if person: + work_schedule.Creators = [person] + work_schedule.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcDateTime") + + if self.settings["work_plan"]: + rel_aggregates = ifcopenshell.api.run("aggregate.assign_object", self.file, **{ + "product": work_schedule, + "relating_object": self.settings["work_plan"] + }) + else: + 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_schedule) + rel_declares.RelatedDefinitions = related_definitions + + return work_schedule \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py new file mode 100644 index 0000000000..26b7042df6 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py @@ -0,0 +1,31 @@ +import ifcopenshell +import ifcopenshell.api + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "work_schedule": None, + "work_plan":None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + ###Project declaration should be removed if the workschedule is assigned to a project.#### + if self.settings["work_schedule"].HasContext: + rel_declares = self.settings["work_schedule"].HasContext[0] + related_definitions = list(rel_declares.RelatedDefinitions) + related_definitions.remove(self.settings["work_schedule"]) + rel_declares.RelatedDefinitions = related_definitions + if self.settings["work_plan"].IsDecomposedBy: + rel_aggregates = self.settings["work_plan"].IsDecomposedBy[0] + related_objects = list(rel_aggregates.RelatedObjects or []) + related_objects.append(self.settings["work_schedule"]) + rel_aggregates.RelatedObjects = related_objects + else: + rel_aggregates = ifcopenshell.api.run("aggregate.assign_object", self.file, **{ + "product": self.settings["work_schedule"], + "relating_object": self.settings["work_plan"] + }) + return rel_aggregates \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py new file mode 100644 index 0000000000..c1dced8bbe --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "work_schedule": 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_schedule"], name, value) \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py new file mode 100644 index 0000000000..7a36eb2348 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py @@ -0,0 +1,14 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"work_schedule": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + # TODO: do a deep purge + if self.settings["work_schedule"].HasContext: + rel_declares = self.settings["work_schedule"].HasContext[0] + if len(rel_declares.RelatedDefinitions) == 1: + self.file.remove(rel_declares) + self.file.remove(self.settings["work_schedule"])