From a777a558bd214be08bb74b8052328d2b8966252f Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Wed, 7 Apr 2021 00:04:57 +0100 Subject: [PATCH] Feature edit work plan (#1418) * ifcopenshell.api modules Persons and Organisations account for IFC4 SCHEMA CHANGE * New Feature to Edit Workplans --- .../bim/module/sequence/__init__.py | 10 ++ .../bim/module/sequence/operator.py | 96 ++++++++++++++++++- .../blenderbim/bim/module/sequence/prop.py | 13 +++ .../blenderbim/bim/module/sequence/ui.py | 45 ++++++++- .../ifcopenshell/api/sequence/data.py | 26 +++-- .../api/sequence/edit_work_plan.py | 13 +++ 6 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 26a02991c2..5eac12ed9c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -2,13 +2,21 @@ import bpy from . import ui, prop, operator classes = ( + operator.LoadWorkPlans, + operator.DisableWorkPlanEditingUI, operator.LoadTasks, operator.DisableTaskEditingUI, operator.AddWorkPlan, + operator.EditWorkPlan, operator.RemoveWorkPlan, + operator.EnableEditingWorkPlan, + operator.DisableEditingWorkPlan, + prop.WorkPlan, + prop.BIMWorkPlanProperties, prop.Task, prop.BIMTaskProperties, ui.BIM_PT_work_plans, + ui.BIM_UL_work_plans, ui.BIM_PT_tasks, ui.BIM_UL_tasks, ) @@ -16,7 +24,9 @@ classes = ( def register(): bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties) + bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties) def unregister(): del bpy.types.Scene.BIMTaskProperties + del bpy.types.Scene.BIMWorkPlanProperties diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 8fb1570267..482e24fb01 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1,9 +1,35 @@ import bpy +import json import ifcopenshell.api from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.sequence.data import Data +class LoadWorkPlans(bpy.types.Operator): + bl_idname = "bim.load_work_plans" + bl_label = "Load Work Plans" + + def execute(self, context): + props = context.scene.BIMWorkPlanProperties + while len(props.work_plans) > 0: + props.work_plans.remove(0) + for ifc_definition_id, work_plan in Data.work_plans.items(): + new = props.work_plans.add() + new.ifc_definition_id = ifc_definition_id + new.name = work_plan["Name"] or "Unnamed" + props.is_editing = True + bpy.ops.bim.disable_editing_work_plan() + return {"FINISHED"} + +class DisableWorkPlanEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_work_plan_editing_ui" + bl_label = "Disable WorkPlan Editing UI" + + def execute(self, context): + context.scene.BIMWorkPlanProperties.is_editing = False + return {"FINISHED"} + + class AddWorkPlan(bpy.types.Operator): bl_idname = "bim.add_work_plan" bl_label = "Add Work Plan" @@ -11,19 +37,83 @@ class AddWorkPlan(bpy.types.Operator): def execute(self, context): ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file()) Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_plans() return {"FINISHED"} +class EditWorkPlan(bpy.types.Operator): + bl_idname = "bim.edit_work_plan" + bl_label = "Edit Work Plan" + + def execute(self, context): + props = context.scene.BIMWorkPlanProperties + attributes = {} + for attribute in props.work_plan_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_plan", self.file, **{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes} + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_plans() + return {"FINISHED"} + class RemoveWorkPlan(bpy.types.Operator): bl_idname = "bim.remove_work_plan" bl_label = "Remove Work Plan" work_plan: bpy.props.IntProperty() def execute(self, context): - ifcopenshell.api.run( - "sequence.remove_work_plan", IfcStore.get_file(), work_plan=IfcStore.get_file().by_id(self.work_plan) - ) + props = context.scene.BIMWorkPlanProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)}) Data.load(IfcStore.get_file()) + bpy.ops.bim.load_work_plans() + return {"FINISHED"} + +class EnableEditingWorkPlan(bpy.types.Operator): + bl_idname = "bim.enable_editing_work_plan" + bl_label = "Enable Editing Work Plan" + work_plan: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkPlanProperties + while len(props.work_plan_attributes) > 0: + props.work_plan_attributes.remove(0) + + data = Data.work_plans[self.work_plan] + + for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkPlan").all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = props.work_plan_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_plan_id = self.work_plan + return {"FINISHED"} + +class DisableEditingWorkPlan(bpy.types.Operator): + bl_idname = "bim.disable_editing_work_plan" + bl_label = "Disable Editing Work Plan" + + def execute(self, context): + context.scene.BIMWorkPlanProperties.active_work_plan_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 226ad5fea0..a1281d2ede 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -23,3 +23,16 @@ class BIMTaskProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) tasks: CollectionProperty(name="Tasks", type=Task) active_task_index: IntProperty(name="Active Task Index") + + +class WorkPlan(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMWorkPlanProperties(PropertyGroup): + work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute) + 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 diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index f38abf8161..e56c0a48c4 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -18,11 +18,50 @@ class BIM_PT_work_plans(Panel): def draw(self, context): if not Data.is_loaded: Data.load(IfcStore.get_file()) + self.props = context.scene.BIMWorkPlanProperties + row = self.layout.row(align=True) + row.label(text="{} Work Plans Found".format(len(Data.work_plans)), icon="TEXT") + if self.props.is_editing: + row.operator("bim.add_work_plan", text="", icon="ADD") + row.operator("bim.disable_work_plan_editing_ui", text="", icon="CHECKMARK") + else: + row.operator("bim.load_work_plans", text="", icon="GREASEPENCIL") - row = self.layout.row() - row.operator("bim.add_work_plan", icon="ADD") + if self.props.is_editing: + self.layout.template_list( + "BIM_UL_work_plans", + "", + self.props, + "work_plans", + self.props, + "active_work_plan_index", + ) - for work_plan_id, work_plan in Data.work_plans.items(): + if self.props.active_work_plan_id: + self.draw_editable_ui(context) + + def draw_editable_ui(self, context): + for attribute in self.props.work_plan_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_plans(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.BIMWorkPlanProperties.active_work_plan_id == item.ifc_definition_id: + row.operator("bim.edit_work_plan", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_work_plan", text="", icon="X") + elif context.scene.BIMWorkPlanProperties.active_work_plan_id: + row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id + else: + 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 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/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 894a942a40..7253f29625 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -14,18 +14,30 @@ class Data: @classmethod def load(cls, file): + cls._file = file + if not cls._file: + return + cls.load_work_plans() + cls.load_work_schedules() + cls.load_tasks() + cls.is_loaded=True + + @classmethod + def load_work_plans(cls): cls.work_plans = {} - cls.tasks = {} - for work_plan in file.by_type("IfcWorkPlan"): + for work_plan in cls._file.by_type("IfcWorkPlan"): data = work_plan.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"]) - data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"]) + 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"]) + data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]).isoformat() cls.work_plans[work_plan.id()] = data - for task in file.by_type("IfcTask"): + + @classmethod + def load_tasks(cls): + cls.tasks = {} + for task in cls._file.by_type("IfcTask"): cls.tasks[task.id()] = {"Name": task.Name, "Identification": task.Identification or ""} - cls.is_loaded=True diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py new file mode 100644 index 0000000000..3c51b745a2 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "work_plan": 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_plan"], name, value) \ No newline at end of file