From 37dec500fd66b64e4cc0f041a4dacf81e0467c92 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Mon, 29 Aug 2022 21:55:37 +0100 Subject: [PATCH] massive refactor of the sequence module --- src/blenderbim/blenderbim/bim/helper.py | 10 +- .../blenderbim/bim/module/pset/operator.py | 3 +- .../bim/module/resource/operator.py | 3 +- .../blenderbim/bim/module/resource/prop.py | 6 +- .../bim/module/sequence/__init__.py | 6 +- .../blenderbim/bim/module/sequence/data.py | 188 ++- .../blenderbim/bim/module/sequence/helper.py | 62 +- .../bim/module/sequence/operator.py | 1181 +++-------------- .../blenderbim/bim/module/sequence/prop.py | 29 +- .../blenderbim/bim/module/sequence/ui.py | 176 ++- src/blenderbim/blenderbim/core/sequence.py | 424 +++++- src/blenderbim/blenderbim/core/tool.py | 78 +- src/blenderbim/blenderbim/tool/__init__.py | 2 +- src/blenderbim/blenderbim/tool/sequence.py | 744 ++++++++++- src/blenderbim/test/bim/feature/pset.feature | 4 +- .../test/bim/feature/sequence.feature | 447 ++++++- .../api/sequence/cascade_schedule.py | 2 +- .../ifcopenshell/api/sequence/data.py | 20 - .../api/sequence/remove_work_calendar.py | 7 + 19 files changed, 2208 insertions(+), 1184 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 06508b50fa..851a54713f 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +# from datetime import date import bpy import json import math @@ -46,6 +47,11 @@ def draw_attribute(attribute, layout, copy_operator=None): value_name, text=attribute.name, ) + if "ScheduleDuration" in attribute.name: + layout.prop(bpy.context.scene.BIMDuration, "duration_days",text="D") + layout.prop(bpy.context.scene.BIMDuration, "duration_hours",text="H") + layout.prop(bpy.context.scene.BIMDuration, "duration_minutes",text="M") + if attribute.is_optional: layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") if copy_operator: @@ -78,6 +84,7 @@ def import_attribute(attribute, props, data, callback=None): new.is_optional = attribute.optional() new.data_type = data_type if isinstance(data_type, str) else "" is_handled_by_callback = callback(attribute.name(), new, data) if callback else None + if is_handled_by_callback: pass # Our job is done elif is_handled_by_callback is False: @@ -96,8 +103,7 @@ def import_attribute(attribute, props, data, callback=None): new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) if data[attribute.name()]: new.enum_value = data[attribute.name()] - - + def export_attributes(props, callback=None): attributes = {} for prop in props: diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 0b1a523efe..9a5e714e20 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -102,7 +102,8 @@ class EnablePsetEditing(bpy.types.Operator): def execute(self, context): self.props = get_pset_props(context, self.obj, self.obj_type) self.props.properties.clear() - + ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type) + Data.load(IfcStore.get_file(), ifc_definition_id) data = Data.psets if self.pset_id in Data.psets else Data.qtos pset_data = data[self.pset_id] self.props.active_pset_name = pset_data["Name"] diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 412101ef71..016b8aa11d 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -40,6 +40,7 @@ class LoadResources(bpy.types.Operator): self.tprops.resources.clear() self.contracted_resources = json.loads(self.props.contracted_resources) + Data.load(IfcStore.get_file()) for resource_id, data in Data.resources.items(): if not data["HasContext"]: continue @@ -116,7 +117,7 @@ class LoadResourceProperties(bpy.types.Operator): class DisableEditingResource(bpy.types.Operator): bl_idname = "bim.disable_editing_resource" - bl_label = "Disable Editing Workplan" + bl_label = "Disable Editing Resources" bl_options = {"REGISTER", "UNDO"} def execute(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/resource/prop.py b/src/blenderbim/blenderbim/bim/module/resource/prop.py index 178171cd37..0a81516cd1 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/prop.py +++ b/src/blenderbim/blenderbim/bim/module/resource/prop.py @@ -20,6 +20,7 @@ import bpy import ifcopenshell.api from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.resource.data import Data +import blenderbim.bim.module.pset.data from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( @@ -70,6 +71,9 @@ def get_quantity_types(self, context): return quantitytypes_enum +def update_active_resource_index(self, context): + blenderbim.bim.module.pset.data.refresh() + class Resource(PropertyGroup): name: StringProperty(name="Name", update=updateResourceName) ifc_definition_id: IntProperty(name="IFC Definition ID") @@ -85,7 +89,7 @@ class BIMResourceTreeProperties(PropertyGroup): class BIMResourceProperties(PropertyGroup): resource_attributes: CollectionProperty(name="Resource Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing") - active_resource_index: IntProperty(name="Active Resource Index") + active_resource_index: IntProperty(name="Active Resource Index", update=update_active_resource_index) active_resource_id: IntProperty(name="Active Resource Id") contracted_resources: StringProperty(name="Contracted Resources", default="[]") is_resource_update_enabled: BoolProperty(name="Is Resource Update Enabled", default=True) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 5c36c39b27..8598228e0d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -63,7 +63,7 @@ classes = ( operator.EnableEditingTaskCalendar, operator.EnableEditingTaskSequence, operator.EnableEditingTaskTime, - operator.EnableEditingTasks, + operator.EnableEditingWorkScheduleTasks, operator.EnableEditingWorkCalendar, operator.EnableEditingWorkCalendarTimes, operator.EnableEditingWorkPlan, @@ -92,6 +92,7 @@ classes = ( operator.RemoveWorkSchedule, operator.RemoveWorkTime, operator.SelectTaskRelatedProducts, + operator.SelectTaskRelatedInputs, operator.SetTaskSortColumn, operator.UnassignLagTime, operator.UnassignPredecessor, @@ -114,6 +115,7 @@ classes = ( prop.BIMWorkCalendarProperties, prop.DatePickerProperties, prop.BIMDateTextProperties, + prop.BIMDuration, ui.BIM_PT_work_plans, ui.BIM_PT_work_schedules, ui.BIM_PT_work_calendars, @@ -144,6 +146,7 @@ def register(): bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties) bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties) bpy.types.Scene.DatePickerProperties = bpy.props.PointerProperty(type=prop.DatePickerProperties) + bpy.types.Scene.BIMDuration = bpy.props.PointerProperty(type=prop.BIMDuration) bpy.types.TextCurve.BIMDateTextProperties = bpy.props.PointerProperty(type=prop.BIMDateTextProperties) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) @@ -155,6 +158,7 @@ def unregister(): del bpy.types.Scene.BIMTaskTreeProperties del bpy.types.Scene.BIMWorkCalendarProperties del bpy.types.Scene.DatePickerProperties + del bpy.types.Scene.BIMDuration del bpy.types.TextCurve.BIMDateTextProperties bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/data.py b/src/blenderbim/blenderbim/bim/module/sequence/data.py index cc948bbe49..75d19b5e6f 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/data.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/data.py @@ -18,6 +18,7 @@ import bpy import blenderbim.tool as tool +import ifcopenshell def refresh(): @@ -25,20 +26,193 @@ def refresh(): class SequenceData: + data = {} is_loaded = False - number_of_work_plans_loaded = 0 - number_of_work_schedules_loaded = 0 - number_of_tasks_loaded = 0 - tasks = {} @classmethod def load(cls): + cls.data = { + "has_work_plans": cls.has_work_plans(), + "has_work_schedules": cls.has_work_schedules(), + "has_work_calendars": cls.has_work_calendars(), + } cls.load_work_plans() + cls.load_work_schedules() + cls.load_work_calendars() + cls.load_work_times() + cls.load_recurrence_patterns() + cls.load_time_periods() + cls.load_sequences() + cls.load_lag_times() + cls.load_task_times() + cls.load_tasks() cls.is_loaded = True + @classmethod + def has_work_plans(cls): + return bool(tool.Ifc.get().by_type("IfcWorkPlan")) + + @classmethod + def has_work_calendars(cls): + return bool(tool.Ifc.get().by_type("IfcWorkCalendar")) + + @classmethod + def number_of_work_plans_loaded(cls): + return len(tool.Ifc.get().by_type("IfcWorkPlan")) + + @classmethod + def number_of_work_schedules_loaded(cls): + return len(tool.Ifc.get().by_type("IfcWorkSchedule")) + + @classmethod + def has_work_schedules(cls): + return bool(tool.Ifc.get().by_type("IfcWorkSchedule")) + @classmethod def load_work_plans(cls): - cls.work_plans = {} - cls.number_of_work_plans_loaded = len(tool.Ifc.get().by_type("IfcWorkPlan")) + cls.data["work_plans"] = {} for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"): - cls.work_plans[work_plan.id()] = {"Name": work_plan.Name} + data = {"Name": work_plan.Name} + data["IsDecomposedBy"] = [] + for rel in work_plan.IsDecomposedBy: + data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects]) + cls.data["work_plans"][work_plan.id()] = data + cls.data["number_of_work_plans_loaded"] = cls.number_of_work_plans_loaded() + + @classmethod + def load_work_schedules(cls): + cls.data["work_schedules"] = {} + for work_schedule in tool.Ifc.get().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"]) if data["CreationDate"] else "" + ) + data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"]) if data["StartTime"] else "" + data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]) if data["FinishTime"] else "" + data["RelatedObjects"] = [] + for rel in work_schedule.Controls: + for obj in rel.RelatedObjects: + if obj.is_a("IfcTask"): + data["RelatedObjects"].append(obj.id()) + cls.data["work_schedules"][work_schedule.id()] = data + + cls.data["number_of_work_schedules_loaded"] = cls.number_of_work_schedules_loaded() + + @classmethod + def load_work_calendars(cls): + cls.data["work_calendars"] = {} + for work_calendar in tool.Ifc.get().by_type("IfcWorkCalendar"): + data = work_calendar.get_info() + del data["OwnerHistory"] + data["WorkingTimes"] = [t.id() for t in work_calendar.WorkingTimes or []] + data["ExceptionTimes"] = [t.id() for t in work_calendar.ExceptionTimes or []] + cls.data["work_calendars"][work_calendar.id()] = data + + cls.data["number_of_work_calendars_loaded"] = len(cls.data["work_calendars"].keys()) + + @classmethod + def load_work_times(cls): + cls.data["work_times"] = {} + for work_time in tool.Ifc.get().by_type("IfcWorkTime"): + data = work_time.get_info() + data["Start"] = ifcopenshell.util.date.ifc2datetime(data["Start"]) if data["Start"] else None + data["Finish"] = ifcopenshell.util.date.ifc2datetime(data["Finish"]) if data["Finish"] else None + data["RecurrencePattern"] = work_time.RecurrencePattern.id() if work_time.RecurrencePattern else None + cls.data["work_times"][work_time.id()] = data + + @classmethod + def load_recurrence_patterns(cls): + cls.data["recurrence_patterns"] = {} + for recurrence_pattern in tool.Ifc.get().by_type("IfcRecurrencePattern"): + data = recurrence_pattern.get_info() + data["TimePeriods"] = [t.id() for t in recurrence_pattern.TimePeriods or []] + cls.data["recurrence_patterns"][recurrence_pattern.id()] = data + + @classmethod + def load_sequences(cls): + cls.data["sequences"] = {} + for sequence in tool.Ifc.get().by_type("IfcRelSequence"): + data = sequence.get_info() + data["RelatingProcess"] = sequence.RelatingProcess.id() + data["RelatedProcess"] = sequence.RelatedProcess.id() + data["TimeLag"] = sequence.TimeLag.id() if sequence.TimeLag else None + cls.data["sequences"][sequence.id()] = data + + @classmethod + def load_time_periods(cls): + cls.data["time_periods"] = {} + for time_period in tool.Ifc.get().by_type("IfcTimePeriod"): + cls.data["time_periods"][time_period.id()] = { + "StartTime": ifcopenshell.util.date.ifc2datetime(time_period.StartTime), + "EndTime": ifcopenshell.util.date.ifc2datetime(time_period.EndTime), + } + + @classmethod + def load_task_times(cls): + cls.data["task_times"] = {} + for task_time in tool.Ifc.get().by_type("IfcTaskTime"): + data = task_time.get_info() + 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) + elif key == "ScheduleDuration": + data[key] = ifcopenshell.util.date.ifc2datetime(value) + cls.data["task_times"][task_time.id()] = data + + @classmethod + def load_lag_times(cls): + cls.data["lag_times"] = {} + for lag_time in tool.Ifc.get().by_type("IfcLagTime"): + data = lag_time.get_info() + if data["LagValue"]: + if data["LagValue"].is_a("IfcDuration"): + data["LagValue"] = ifcopenshell.util.date.ifc2datetime(data["LagValue"].wrappedValue) + else: + data["LagValue"] = float(data["LagValue"].wrappedValue) + cls.data["lag_times"][lag_time.id()] = data + + @classmethod + def load_tasks(cls): + cls.data["tasks"] = {} + for task in tool.Ifc.get().by_type("IfcTask"): + data = task.get_info() + del data["OwnerHistory"] + data["HasAssignmentsWorkCalendar"] = [] + data["RelatedObjects"] = [] + data["Inputs"] = [] + data["Controls"] = [] + data["Outputs"] = [] + data["Resources"] = [] + data["IsPredecessorTo"] = [] + data["IsSuccessorFrom"] = [] + if task.TaskTime: + 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["Nests"] = [r.RelatingObject.id() for r in task.Nests or []] + [ + data["Outputs"].append(r.RelatingProduct.id()) + for r in task.HasAssignments + if r.is_a("IfcRelAssignsToProduct") + ] + [ + data["Resources"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcResource")]) + for r in task.OperatesOn + ] + [ + data["Controls"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcControl")]) + for r in task.OperatesOn + ] + [data["Inputs"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcProduct")]) for r in task.OperatesOn] + [data["IsPredecessorTo"].append(rel.id()) for rel in task.IsPredecessorTo or []] + [data["IsSuccessorFrom"].append(rel.id()) for rel in task.IsSuccessorFrom or []] + for rel in task.HasAssignments: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl: + if rel.RelatingControl.is_a("IfcWorkCalendar"): + data["HasAssignmentsWorkCalendar"].append(rel.RelatingControl.id()) + cls.data["tasks"][task.id()] = data diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index 385724818a..a1b03ce7ee 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -16,38 +16,21 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import bpy -import math import isodate -import datetime from dateutil import parser -from ifcopenshell.api.sequence.data import Data +import ifcopenshell.util.date as ifcdateutils -def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, is_latest=False): - task = Data.tasks[ifc_definition_id] - if task["TaskTime"]: - current_date = Data.task_times[task["TaskTime"]][attribute_name] +def derive_date(task, attribute_name, date=None, is_earliest=False, is_latest=False): + if task.TaskTime: + current_date = ( + ifcdateutils.ifc2datetime(getattr(task.TaskTime, attribute_name)) + if getattr(task.TaskTime, attribute_name) + else "" + ) if current_date: return current_date - for subtask in task["RelatedObjects"]: - current_date = derive_date(subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest) - if is_earliest: - if current_date and (date is None or current_date < date): - date = current_date - if is_latest: - if current_date and (date is None or current_date > date): - date = current_date - return date - - -def derive_duration(ifc_definition_id, attribute_name): - task = Data.tasks[ifc_definition_id] - if task["TaskTime"]: - current_date = Data.task_times[task["TaskTime"]][attribute_name] - if current_date: - return current_date - for subtask in task["RelatedObjects"]: + for subtask in get_nested_tasks(task): current_date = derive_date(subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest) if is_earliest: if current_date and (date is None or current_date < date): @@ -79,3 +62,30 @@ def canonicalise_time(time): if not time: return "-" return time.strftime("%d/%m/%y") + + +def get_nested_tasks(task): + tasks = [] + for rel in task.IsNestedBy: + for object in rel.RelatedObjects: + if object.is_a("IfcTask"): + tasks.append(object) + return tasks + + +def get_parent_task(task): + return task.Nests[0].RelatingObject if task.Nests and task.Nests[0].RelatingObject.is_a("IfcTask") else None + + +def get_task_work_schedule(task): + parent_task = get_parent_task(task) + if parent_task: + get_task_work_schedule(parent_task) + else: + schedules = [ + rel.RelatingControl + for rel in task.HasAssignments + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule") + ] + print(f"Returning {schedules}") + return schedules diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index a3e8d2e7c0..dee5dc5d35 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -60,7 +60,7 @@ class AddWorkPlan(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.add_work_plan(tool.Ifc, tool.Sequence) + core.add_work_plan(tool.Ifc) class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator): @@ -69,7 +69,11 @@ class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Edit Work Plan" def _execute(self, context): - core.edit_work_plan(tool.Ifc, tool.Sequence) + core.edit_work_plan( + tool.Ifc, + tool.Sequence, + work_plan=tool.Ifc.get().by_id(context.scene.BIMWorkPlanProperties.active_work_plan_id), + ) class RemoveWorkPlan(bpy.types.Operator, tool.Ifc.Operator): @@ -79,7 +83,7 @@ class RemoveWorkPlan(bpy.types.Operator, tool.Ifc.Operator): work_plan: bpy.props.IntProperty() def _execute(self, context): - core.remove_work_plan(tool.Ifc, tool.Sequence, work_plan=tool.Ifc.get().by_id(self.work_plan)) + core.remove_work_plan(tool.Ifc, work_plan=tool.Ifc.get().by_id(self.work_plan)) class EnableEditingWorkPlan(bpy.types.Operator, tool.Ifc.Operator): @@ -108,126 +112,71 @@ class EnableEditingWorkPlanSchedules(bpy.types.Operator): work_plan: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkPlanProperties - props.active_work_plan_id = self.work_plan - props.editing_type = "SCHEDULES" + core.enable_editing_work_plan_schedules(tool.Sequence, work_plan=tool.Ifc.get().by_id(self.work_plan)) return {"FINISHED"} -class AssignWorkSchedule(bpy.types.Operator): +class AssignWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_work_schedule" bl_label = "Assign Work Schedule" bl_options = {"REGISTER", "UNDO"} work_plan: bpy.props.IntProperty() work_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "aggregate.assign_object", - self.file, - **{ - "relating_object": self.file.by_id(self.work_plan), - "product": self.file.by_id(self.work_schedule), - }, + core.assign_work_schedule( + tool.Ifc, + tool.Sequence, + work_plan=tool.Ifc.get().by_id(self.work_plan), + work_schedule=tool.Ifc.get().by_id(self.work_schedule), ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} -class UnassignWorkSchedule(bpy.types.Operator): +class UnassignWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_work_schedule" bl_label = "Unassign Work Schedule" bl_options = {"REGISTER", "UNDO"} work_plan: bpy.props.IntProperty() work_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "aggregate.unassign_object", - self.file, - **{ - "relating_object": self.file.by_id(self.work_plan), - "product": self.file.by_id(self.work_schedule), - }, + core.unassign_work_schedule( + tool.Ifc, + work_plan=tool.Ifc.get().by_id(self.work_plan), + work_schedule=tool.Ifc.get().by_id(self.work_schedule), ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} -class AddWorkSchedule(bpy.types.Operator): +class AddWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_work_schedule" bl_label = "Add Work Schedule" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file()) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_work_schedule(tool.Ifc) -class EditWorkSchedule(bpy.types.Operator): +class EditWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_work_schedule" bl_label = "Edit Work Schedule" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - attributes = blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, self.export_attributes) - 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}, + core.edit_work_schedule( + tool.Ifc, + tool.Sequence, + work_schedule=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_work_schedule_id), ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_work_schedule() - return {"FINISHED"} - - def export_attributes(self, attributes, prop): - if "Date" in prop.name or "Time" in prop.name: - if prop.is_null: - attributes[prop.name] = None - return True - attributes[prop.name] = helper.parse_datetime(prop.string_value) - return True - elif prop.name == "Duration" or prop.name == "TotalFloat": - if prop.is_null: - attributes[prop.name] = None - return True - attributes[prop.name] = helper.parse_duration(prop.string_value) - return True -class RemoveWorkSchedule(bpy.types.Operator): +class RemoveWorkSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_work_schedule" bl_label = "Remove Work Schedule" bl_options = {"REGISTER", "UNDO"} work_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - 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(self.file) - return {"FINISHED"} + core.remove_work_schedule(tool.Ifc, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) class EnableEditingWorkSchedule(bpy.types.Operator): @@ -237,151 +186,30 @@ class EnableEditingWorkSchedule(bpy.types.Operator): work_schedule: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.props.active_work_schedule_id = self.work_schedule - self.props.work_schedule_attributes.clear() - self.enable_editing_work_schedule() - self.props.editing_type = "WORK_SCHEDULE" + core.enable_editing_work_schedule(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) return {"FINISHED"} - def enable_editing_work_schedule(self): - data = Data.work_schedules[self.work_schedule] - blenderbim.bim.helper.import_attributes( - "IfcWorkSchedule", self.props.work_schedule_attributes, data, self.import_attributes - ) - - def import_attributes(self, name, prop, data): - if name in ["CreationDate", "StartTime", "FinishTime"]: - prop.string_value = "" if prop.is_null else data[name].isoformat() - return True - - -class EnableEditingTasks(bpy.types.Operator): - bl_idname = "bim.enable_editing_tasks" +class EnableEditingWorkScheduleTasks(bpy.types.Operator): + bl_idname = "bim.enable_editing_work_schedule_tasks" bl_label = "Enable Editing Tasks" bl_options = {"REGISTER", "UNDO"} work_schedule: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties - self.props.active_work_schedule_id = self.work_schedule - self.tprops.tasks.clear() - - self.contracted_tasks = json.loads(self.props.contracted_tasks) - self.sort_keys = { - i: self.get_sort_key(Data.tasks[i]) for i in Data.work_schedules[self.work_schedule]["RelatedObjects"] - } - - related_object_ids = sorted(self.sort_keys, key=self.natural_sort_key) - if self.props.is_sort_reversed: - related_object_ids.reverse() - - for related_object_id in related_object_ids: - self.create_new_task_li(related_object_id, 0) - bpy.ops.bim.load_task_properties() - self.props.editing_type = "TASKS" + core.enable_editing_work_schedule_tasks(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) return {"FINISHED"} - def create_new_task_li(self, related_object_id, level_index): - task = Data.tasks[related_object_id] - new = self.tprops.tasks.add() - new.ifc_definition_id = related_object_id - new.is_expanded = related_object_id not in self.contracted_tasks - new.level_index = level_index - if task["RelatedObjects"]: - new.has_children = True - if new.is_expanded: - self.sort_keys = {i: self.get_sort_key(Data.tasks[i]) for i in task["RelatedObjects"]} - related_object_ids = sorted(self.sort_keys, key=self.natural_sort_key) - if self.props.is_sort_reversed: - related_object_ids.reverse() - for related_object_id in related_object_ids: - self.create_new_task_li(related_object_id, level_index + 1) - - def get_sort_key(self, task): - # Sorting only applies to actual tasks, not the WBS - if task["RelatedObjects"]: - # Sorry for the hack - return "0000000000" + (task["Identification"] or "") - if not self.props.sort_column: - return task["Identification"] or "" - column_type, name = self.props.sort_column.split(".") - if column_type == "IfcTask": - return task.get(name) - elif column_type == "IfcTaskTime" and task.get("TaskTime"): - task_time = Data.task_times[task.get("TaskTime")].get(name) - return task["Identification"] or "" - - def natural_sort_key(self, i, _nsre=re.compile("([0-9]+)")): - s = self.sort_keys[i] - return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] - class LoadTaskProperties(bpy.types.Operator): bl_idname = "bim.load_task_properties" bl_label = "Load Task Properties" bl_options = {"REGISTER", "UNDO"} - task: bpy.props.IntProperty() def execute(self, context): - self.file = IfcStore.get_file() - self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties - self.props.is_task_update_enabled = False - for item in self.tprops.tasks: - if self.task and item.ifc_definition_id != self.task: - continue - task = Data.tasks[item.ifc_definition_id] - item.name = task["Name"] or "Unnamed" - item.identification = task["Identification"] or "XXX" - if self.props.active_task_id: - item.is_predecessor = self.props.active_task_id in [ - Data.sequences[r]["RelatedProcess"] for r in task["IsPredecessorTo"] - ] - item.is_successor = self.props.active_task_id in [ - Data.sequences[r]["RelatingProcess"] for r in task["IsSuccessorFrom"] - ] - - calendar = ifcopenshell.util.sequence.derive_calendar(self.file.by_id(item.ifc_definition_id)) - if calendar: - calendar = Data.work_calendars[calendar.id()] - if task["HasAssignmentsWorkCalendar"]: - item.calendar = calendar["Name"] or "Unnamed" - else: - item.calendar = "" - item.derived_calendar = calendar["Name"] or "Unnamed" if calendar else "" - - if task["TaskTime"]: - task_time = Data.task_times[task["TaskTime"]] - item.start = self.canonicalise_time(task_time["ScheduleStart"]) - item.finish = self.canonicalise_time(task_time["ScheduleFinish"]) - item.duration = ( - isodate.duration_isoformat(task_time["ScheduleDuration"]) if task_time["ScheduleDuration"] else "-" - ) - else: - derived_start = helper.derive_date(item.ifc_definition_id, "ScheduleStart", is_earliest=True) - derived_finish = helper.derive_date(item.ifc_definition_id, "ScheduleFinish", is_latest=True) - item.derived_start = self.canonicalise_time(derived_start) if derived_start else "" - item.derived_finish = self.canonicalise_time(derived_finish) if derived_finish else "" - if derived_start and derived_finish and calendar: - derived_duration = ifcopenshell.util.sequence.count_working_days( - derived_start, derived_finish, self.file.by_id(calendar["id"]) - ) - item.derived_duration = f"P{derived_duration}D" - item.start = "-" - item.finish = "-" - item.duration = "-" - - self.props.is_task_update_enabled = True + core.load_task_properties(tool.Sequence) return {"FINISHED"} - 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" @@ -389,44 +217,28 @@ class DisableEditingWorkSchedule(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + core.disable_editing_work_schedule(tool.Sequence) return {"FINISHED"} -class AddTask(bpy.types.Operator): +class AddTask(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_task" bl_label = "Add Task" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, parent_task=self.file.by_id(self.task)) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) - return {"FINISHED"} + core.add_task(tool.Ifc, tool.Sequence, parent_task=tool.Ifc.get().by_id(self.task)) -class AddSummaryTask(bpy.types.Operator): +class AddSummaryTask(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_summary_task" bl_label = "Add Task" bl_options = {"REGISTER", "UNDO"} work_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=self.file.by_id(self.work_schedule)) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) - return {"FINISHED"} + core.add_summary_task(tool.Ifc, tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) class ExpandTask(bpy.types.Operator): @@ -436,13 +248,7 @@ class ExpandTask(bpy.types.Operator): task: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - contracted_tasks = json.loads(props.contracted_tasks) - contracted_tasks.remove(self.task) - props.contracted_tasks = json.dumps(contracted_tasks) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) + core.expand_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} @@ -453,129 +259,41 @@ class ContractTask(bpy.types.Operator): task: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - contracted_tasks = json.loads(props.contracted_tasks) - contracted_tasks.append(self.task) - props.contracted_tasks = json.dumps(contracted_tasks) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) + core.contract_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} -class RemoveTask(bpy.types.Operator): +class RemoveTask(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_task" bl_label = "Remove Task" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - edited_task_ifc = self.file.by_id(props.active_task_id) if props.active_task_id else None - - ifcopenshell.api.run( - "sequence.remove_task", - self.file, - task=self.file.by_id(self.task), - ) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) - - if edited_task_ifc: - if not any( - task - for task in context.scene.BIMTaskTreeProperties.tasks - if task.ifc_definition_id == props.active_task_id - and self.file.by_id(props.active_task_id) == edited_task_ifc - ): # Task was deleted - bpy.ops.bim.disable_editing_task() - - return {"FINISHED"} + core.remove_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class EnableEditingTaskTime(bpy.types.Operator): +class EnableEditingTaskTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_task_time" bl_label = "Enable Editing Task Time" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - 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() - - props.task_time_attributes.clear() - - data = Data.task_times[task_time_id] - - blenderbim.bim.helper.import_attributes("IfcTaskTime", props.task_time_attributes, data, self.import_attributes) - props.active_task_time_id = task_time_id - props.active_task_id = self.task - props.editing_task_type = "TASKTIME" - return {"FINISHED"} - - def import_attributes(self, name, prop, data): - if prop.data_type == "string": - if isinstance(data[name], datetime): - prop.string_value = "" if prop.is_null else data[name].isoformat() - return True - elif isinstance(data[name], isodate.Duration): - prop.string_value = ( - "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration") - ) - return True - - 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(self.file) - return task_time + core.enable_editing_task_time(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class EditTaskTime(bpy.types.Operator): +class EditTaskTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_task_time" bl_label = "Edit Task Time" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_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}, + core.edit_task_time( + tool.Ifc, + tool.Sequence, + task_time=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_task_time_id), ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_task_time() - bpy.ops.bim.load_task_properties(task=props.active_task_id) - return {"FINISHED"} - - def export_attributes(self, attributes, prop): - if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": - if prop.is_null: - attributes[prop.name] = None - return True - attributes[prop.name] = helper.parse_datetime(prop.string_value) - return True - elif prop.name == "ScheduleDuration": - if prop.is_null: - attributes[prop.name] = None - return True - # TODO make this parse PT32 as P4D - attributes[prop.name] = helper.parse_duration(prop.string_value) - return True class EnableEditingTask(bpy.types.Operator): @@ -585,13 +303,7 @@ class EnableEditingTask(bpy.types.Operator): task: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - props.task_attributes.clear() - data = Data.tasks[self.task] - - blenderbim.bim.helper.import_attributes("IfcTask", props.task_attributes, data) - props.active_task_id = self.task - props.editing_task_type = "ATTRIBUTES" + core.enable_editing_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} @@ -601,262 +313,132 @@ class DisableEditingTask(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkScheduleProperties.active_task_id = 0 - context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + core.disable_editing_task(tool.Sequence) return {"FINISHED"} -class EditTask(bpy.types.Operator): +class EditTask(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_task" bl_label = "Edit Task" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - attributes = blenderbim.bim.helper.export_attributes(props.task_attributes) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.edit_task", self.file, **{"task": self.file.by_id(props.active_task_id), "attributes": attributes} + core.edit_task( + tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_task_id) ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_task() - bpy.ops.bim.load_task_properties(task=props.active_task_id) - return {"FINISHED"} -class CopyTaskAttribute(bpy.types.Operator): +class CopyTaskAttribute(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.copy_task_attribute" bl_label = "Copy Task Attribute" bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - value = context.scene.BIMWorkScheduleProperties.task_attributes.get(self.name).get_value() - self.file = IfcStore.get_file() - props = context.scene.BIMTaskTreeProperties - for task in props.tasks: - if task.is_selected: - ifcopenshell.api.run( - "sequence.edit_task", - self.file, - task=self.file.by_id(task.ifc_definition_id), - attributes={self.name: value}, - ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.copy_task_attribute(tool.Ifc, tool.Sequence, attribute_name=self.name) -class AssignPredecessor(bpy.types.Operator): +class AssignPredecessor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_predecessor" bl_label = "Assign Predecessor" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - rel = ifcopenshell.api.run( - "sequence.assign_sequence", - self.file, - relating_process=IfcStore.get_file().by_id(self.task), - related_process=IfcStore.get_file().by_id(props.active_task_id), - ) - Data.load(self.file) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.assign_predecessor(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class AssignSuccessor(bpy.types.Operator): +class AssignSuccessor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_successor" bl_label = "Assign Successor" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - rel = ifcopenshell.api.run( - "sequence.assign_sequence", - self.file, - relating_process=IfcStore.get_file().by_id(props.active_task_id), - related_process=IfcStore.get_file().by_id(self.task), - ) - Data.load(self.file) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.assign_successor(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class UnassignPredecessor(bpy.types.Operator): +class UnassignPredecessor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_predecessor" bl_label = "Unassign Predecessor" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.unassign_sequence", - self.file, - relating_process=IfcStore.get_file().by_id(self.task), - related_process=IfcStore.get_file().by_id(props.active_task_id), - ) - Data.load(self.file) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.unassign_predecessor(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class UnassignSuccessor(bpy.types.Operator): +class UnassignSuccessor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_successor" bl_label = "Unassign Successor" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.unassign_sequence", - self.file, - relating_process=self.file.by_id(props.active_task_id), - related_process=self.file.by_id(self.task), - ) - Data.load(self.file) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.unassign_successor(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class AssignProduct(bpy.types.Operator): +class AssignProduct(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_product" bl_label = "Assign Product" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - relating_product: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + relating_product: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - relating_products = ( - [bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects - ) - for relating_product in relating_products: - if not relating_product.BIMObjectProperties.ifc_definition_id: - continue - ifcopenshell.api.run( - "sequence.assign_product", - self.file, - relating_product=self.file.by_id(relating_product.BIMObjectProperties.ifc_definition_id), - related_object=self.file.by_id(self.task), + if self.relating_product: + core.assign_products( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + products=[tool.Ifc.get().by_id(self.relating_product)], ) - Data.load(self.file) - bpy.ops.bim.load_task_outputs() - return {"FINISHED"} + else: + core.assign_products(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class UnassignProduct(bpy.types.Operator): +class UnassignProduct(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_product" bl_label = "Unassign Product" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - relating_product: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + relating_product: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - relating_products = ( - [bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects - ) - for relating_product in relating_products: - if not relating_product.BIMObjectProperties.ifc_definition_id: - continue - ifcopenshell.api.run( - "sequence.unassign_product", - self.file, - relating_product=self.file.by_id(relating_product.BIMObjectProperties.ifc_definition_id), - related_object=self.file.by_id(self.task), + if self.relating_product: + core.unassign_products( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + products=[tool.Ifc.get().by_id(self.relating_product)], ) - Data.load(self.file) - bpy.ops.bim.load_task_outputs() - return {"FINISHED"} + else: + core.unassign_products(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) -class AssignProcess(bpy.types.Operator): +class AssignProcess(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_process" bl_label = "Assign Process" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() related_object_type: bpy.props.StringProperty() - related_object: bpy.props.StringProperty() - resource: bpy.props.IntProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + related_object: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() if self.related_object_type == "RESOURCE": - self.assign_resource() + core.assign_resource(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) elif self.related_object_type == "PRODUCT": - self.assign_product(context) + if self.related_object: + core.assign_input_products( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + products=[tool.Ifc.get().by_id(self.related_object)], + ) + else: + core.assign_input_products(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) elif self.related_object_type == "CONTROL": pass # TODO - return {"FINISHED"} - - def assign_resource(self): - task = self.file.by_id(self.task) - resource = self.file.by_id(self.resource) - subresource = ifcopenshell.api.run( - "resource.add_resource", - self.file, - **{"parent_resource": resource, "ifc_class": resource.is_a(), "name": resource.Name}, - ) - ifcopenshell.api.run( - "sequence.assign_process", self.file, **{"related_object": subresource, "relating_process": task} - ) - ResourceData.load(self.file) - Data.load(self.file) - bpy.ops.bim.load_resources() - bpy.ops.bim.load_task_resources() - - def assign_product(self, context): - task = self.file.by_id(self.task) - related_objects = ( - [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects - ) - for related_object in related_objects: - if not related_object.BIMObjectProperties.ifc_definition_id: - continue - ifcopenshell.api.run( - "sequence.assign_process", - self.file, - related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id), - relating_process=task, - ) - Data.load(self.file) - bpy.ops.bim.load_task_inputs() class UnassignProcess(bpy.types.Operator): @@ -865,49 +447,35 @@ class UnassignProcess(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() related_object_type: bpy.props.StringProperty() - related_object: bpy.props.StringProperty() + related_object: bpy.props.IntProperty() resource: bpy.props.IntProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() if self.related_object_type == "RESOURCE": - self.unassign_resource() + core.unassign_resource( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + resource=tool.Ifc.get().by_id(self.resource), + ) + elif self.related_object_type == "PRODUCT": - self.unassign_product(context) + if self.related_object: + core.unassign_input_products( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + products=[tool.Ifc.get().by_id(self.related_object)], + ) + else: + core.unassign_input_products(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) elif self.related_object_type == "CONTROL": pass # TODO return {"FINISHED"} - def unassign_resource(self): - task = self.file.by_id(self.task) - resource = self.file.by_id(self.resource) - ifcopenshell.api.run("sequence.unassign_process", self.file, related_object=resource, relating_process=task) - ifcopenshell.api.run("resource.remove_resource", self.file, resource=resource) - ResourceData.load(self.file) - Data.load(self.file) - bpy.ops.bim.load_resources() - bpy.ops.bim.load_task_resources() - - def unassign_product(self, context): - task = self.file.by_id(self.task) - related_objects = ( - [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects - ) - for related_object in related_objects: - if not related_object.BIMObjectProperties.ifc_definition_id: - continue - ifcopenshell.api.run( - "sequence.unassign_process", - self.file, - related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id), - relating_process=task, - ) - Data.load(self.file) - bpy.ops.bim.load_task_inputs() - class GenerateGanttChart(bpy.types.Operator): bl_idname = "bim.generate_gantt_chart" @@ -926,6 +494,7 @@ class GenerateGanttChart(bpy.types.Operator): "USERDEFINED": "FS", "NOTDEFINED": "FS", } + Data.load(self.file) for task_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]: self.create_new_task_json(task_id) with open(os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f: @@ -971,58 +540,36 @@ class GenerateGanttChart(bpy.types.Operator): self.create_new_task_json(task_id) -class AddWorkCalendar(bpy.types.Operator): +class AddWorkCalendar(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_work_calendar" bl_label = "Add Work Calendar" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file()) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_work_calendar(tool.Ifc) -class EditWorkCalendar(bpy.types.Operator): +class EditWorkCalendar(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_work_calendar" bl_label = "Edit Work Calendar" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkCalendarProperties - attributes = blenderbim.bim.helper.export_attributes(props.work_calendar_attributes) - 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}, + core.edit_work_calendar( + tool.Ifc, + tool.Sequence, + work_calendar=tool.Ifc.get().by_id(context.scene.BIMWorkCalendarProperties.active_work_calendar_id), ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_work_calendar() - return {"FINISHED"} -class RemoveWorkCalendar(bpy.types.Operator): +class RemoveWorkCalendar(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_work_calendar" bl_label = "Remove Work Plan" bl_options = {"REGISTER", "UNDO"} work_calendar: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - 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(self.file) - return {"FINISHED"} + core.remove_work_calendar(tool.Ifc, work_calendar=tool.Ifc.get().by_id(self.work_calendar)) class EnableEditingWorkCalendar(bpy.types.Operator): @@ -1032,14 +579,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator): work_calendar: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkCalendarProperties - self.props.work_calendar_attributes.clear() - - data = Data.work_calendars[self.work_calendar] - - blenderbim.bim.helper.import_attributes("IfcWorkCalendar", self.props.work_calendar_attributes, data) - self.props.active_work_calendar_id = self.work_calendar - self.props.editing_type = "ATTRIBUTES" + core.enable_editing_work_calendar(tool.Sequence, work_calendar=tool.Ifc.get().by_id(self.work_calendar)) return {"FINISHED"} @@ -1049,7 +589,7 @@ class DisableEditingWorkCalendar(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0 + core.disable_editing_work_calendar(tool.Sequence) return {"FINISHED"} @@ -1227,31 +767,19 @@ class EnableEditingWorkCalendarTimes(bpy.types.Operator): work_calendar: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkCalendarProperties - props.active_work_calendar_id = self.work_calendar - props.editing_type = "WORKTIMES" + core.enable_editing_work_calendar_times(tool.Sequence, work_calendar=tool.Ifc.get().by_id(self.work_calendar)) return {"FINISHED"} -class AddWorkTime(bpy.types.Operator): +class AddWorkTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_work_time" bl_label = "Add Work Time" bl_options = {"REGISTER", "UNDO"} work_calendar: bpy.props.IntProperty() time_type: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.add_work_time", - self.file, - **{"work_calendar": self.file.by_id(self.work_calendar), "time_type": self.time_type}, - ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_work_time(tool.Ifc, work_calendar=tool.Ifc.get().by_id(self.work_calendar), time_type=self.time_type) class EnableEditingWorkTime(bpy.types.Operator): @@ -1261,64 +789,9 @@ class EnableEditingWorkTime(bpy.types.Operator): work_time: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkCalendarProperties - self.props.work_time_attributes.clear() - - data = Data.work_times[self.work_time] - - blenderbim.bim.helper.import_attributes( - "IfcWorkTime", self.props.work_time_attributes, data, self.import_attributes - ) - - self.initialise_recurrence_components() - self.load_recurrence_pattern_data(data) - self.props.active_work_time_id = self.work_time + core.enable_editing_work_time(tool.Sequence, work_time=tool.Ifc.get().by_id(self.work_time)) return {"FINISHED"} - def import_attributes(self, name, prop, data): - if name in ["Start", "Finish"]: - prop.string_value = "" if prop.is_null else data[name].isoformat() - return True - - def initialise_recurrence_components(self): - if len(self.props.day_components) == 0: - for i in range(0, 31): - new = self.props.day_components.add() - new.name = str(i + 1) - if len(self.props.weekday_components) == 0: - for d in ["M", "T", "W", "T", "F", "S", "S"]: - new = self.props.weekday_components.add() - new.name = d - if len(self.props.month_components) == 0: - for m in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]: - new = self.props.month_components.add() - new.name = m - - def load_recurrence_pattern_data(self, work_time): - self.props.position = 0 - self.props.interval = 0 - self.props.occurrences = 0 - self.props.start_time = "" - self.props.end_time = "" - for component in self.props.day_components: - component.is_specified = False - for component in self.props.weekday_components: - component.is_specified = False - for component in self.props.month_components: - component.is_specified = False - if not work_time["RecurrencePattern"]: - return - recurrence_pattern = Data.recurrence_patterns[work_time["RecurrencePattern"]] - for attribute in ["Position", "Interval", "Occurrences"]: - if recurrence_pattern[attribute]: - setattr(self.props, attribute.lower(), recurrence_pattern[attribute]) - for component in recurrence_pattern["DayComponent"] or []: - self.props.day_components[component - 1].is_specified = True - for component in recurrence_pattern["WeekdayComponent"] or []: - self.props.weekday_components[component - 1].is_specified = True - for component in recurrence_pattern["MonthComponent"] or []: - self.props.month_components[component - 1].is_specified = True - class DisableEditingWorkTime(bpy.types.Operator): bl_idname = "bim.disable_editing_work_time" @@ -1326,82 +799,27 @@ class DisableEditingWorkTime(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkCalendarProperties.active_work_time_id = 0 + core.disable_editing_work_time(tool.Sequence) return {"FINISHED"} -class EditWorkTime(bpy.types.Operator): +class EditWorkTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_work_time" bl_label = "Edit Work Time" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.props = context.scene.BIMWorkCalendarProperties - attributes = blenderbim.bim.helper.export_attributes(self.props.work_time_attributes) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.edit_work_time", - self.file, - **{"work_time": self.file.by_id(self.props.active_work_time_id), "attributes": attributes}, - ) - - work_time = Data.work_times[self.props.active_work_time_id] - if work_time["RecurrencePattern"]: - self.edit_recurrence_pattern(work_time["RecurrencePattern"]) - - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_work_time() - return {"FINISHED"} - - def edit_recurrence_pattern(self, recurrence_pattern_id): - recurrence_pattern = self.file.by_id(recurrence_pattern_id) - attributes = { - "Interval": self.props.interval if self.props.interval > 0 else None, - "Occurrences": self.props.occurrences if self.props.occurrences > 0 else None, - } - applicable_data = { - "DAILY": ["Interval", "Occurrences"], - "WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"], - "MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"], - "MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"], - "BY_DAY_COUNT": ["Interval", "Occurrences"], - "BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"], - "YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"], - "YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"], - } - if "Position" in applicable_data[recurrence_pattern.RecurrenceType]: - attributes["Position"] = self.props.position if self.props.position != 0 else None - if "DayComponent" in applicable_data[recurrence_pattern.RecurrenceType]: - attributes["DayComponent"] = [i + 1 for i, c in enumerate(self.props.day_components) if c.is_specified] - if "WeekdayComponent" in applicable_data[recurrence_pattern.RecurrenceType]: - attributes["WeekdayComponent"] = [ - i + 1 for i, c in enumerate(self.props.weekday_components) if c.is_specified - ] - if "MonthComponent" in applicable_data[recurrence_pattern.RecurrenceType]: - attributes["MonthComponent"] = [i + 1 for i, c in enumerate(self.props.month_components) if c.is_specified] - ifcopenshell.api.run( - "sequence.edit_recurrence_pattern", - self.file, - **{"recurrence_pattern": recurrence_pattern, "attributes": attributes}, - ) + core.edit_work_time(tool.Ifc, tool.Sequence) -class RemoveWorkTime(bpy.types.Operator): +class RemoveWorkTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_work_time" bl_label = "Remove Work Plan" bl_options = {"REGISTER", "UNDO"} work_time: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.remove_work_time", self.file, **{"work_time": self.file.by_id(self.work_time)}) - Data.load(self.file) + core.remove_work_time(tool.Ifc, work_time=tool.Ifc.get().by_id(self.work_time)) return {"FINISHED"} @@ -1416,13 +834,9 @@ class AssignRecurrencePattern(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.assign_recurrence_pattern", - self.file, - **{"parent": self.file.by_id(self.work_time), "recurrence_type": self.recurrence_type}, + core.assign_recurrence_pattern( + tool.Ifc, work_time=tool.Ifc.get().by_id(self.work_time), recurrence_type=self.recurrence_type ) - Data.load(IfcStore.get_file()) return {"FINISHED"} @@ -1436,66 +850,28 @@ class UnassignRecurrencePattern(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.unassign_recurrence_pattern", - self.file, - **{"recurrence_pattern": self.file.by_id(self.recurrence_pattern)}, - ) - Data.load(self.file) + core.unassign_recurrence_pattern(tool.Ifc, recurrence_pattern=tool.Ifc.get().by_id(self.recurrence_pattern)) return {"FINISHED"} -class AddTimePeriod(bpy.types.Operator): +class AddTimePeriod(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_time_period" bl_label = "Add Time Period" bl_options = {"REGISTER", "UNDO"} recurrence_pattern: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.props = context.scene.BIMWorkCalendarProperties - self.file = IfcStore.get_file() - try: - start_time = parser.parse(self.props.start_time) - end_time = parser.parse(self.props.end_time) - except: - return {"FINISHED"} - ifcopenshell.api.run( - "sequence.add_time_period", - self.file, - **{ - "recurrence_pattern": self.file.by_id(self.recurrence_pattern), - "start_time": start_time, - "end_time": end_time, - }, - ) - self.props.start_time = "" - self.props.end_time = "" - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_time_period(tool.Ifc, tool.Sequence, recurrence_pattern=tool.Ifc.get().by_id(self.recurrence_pattern)) -class RemoveTimePeriod(bpy.types.Operator): +class RemoveTimePeriod(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_time_period" bl_label = "Remove Time Period" bl_options = {"REGISTER", "UNDO"} time_period: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.remove_time_period", - self.file, - **{"time_period": self.file.by_id(self.time_period)}, - ) - Data.load(self.file) - return {"FINISHED"} + core.remove_time_period(tool.Ifc, time_period=tool.Ifc.get().by_id(self.time_period)) class EnableEditingTaskCalendar(bpy.types.Operator): @@ -1505,64 +881,40 @@ class EnableEditingTaskCalendar(bpy.types.Operator): task: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - props.active_task_id = self.task - props.editing_task_type = "CALENDAR" + core.enable_editing_task_calendar(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} -class EditTaskCalendar(bpy.types.Operator): +class EditTaskCalendar(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_task_calendar" bl_label = "Edit Task Calendar" bl_options = {"REGISTER", "UNDO"} work_calendar: bpy.props.IntProperty() task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - task = self.file.by_id(self.task) - ifcopenshell.api.run( - "control.assign_control", - self.file, - **{ - "relating_control": self.file.by_id(self.work_calendar), - "related_object": task, - }, + core.edit_task_calendar( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + work_calendar=tool.Ifc.get().by_id(self.work_calendar), ) - ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=task) - Data.load(IfcStore.get_file()) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} -class RemoveTaskCalendar(bpy.types.Operator): +class RemoveTaskCalendar(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_task_calendar" bl_label = "Remove Task Calendar" bl_options = {"REGISTER", "UNDO"} work_calendar: bpy.props.IntProperty() task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - task = self.file.by_id(self.task) - ifcopenshell.api.run( - "control.unassign_control", - self.file, - **{ - "relating_control": self.file.by_id(self.work_calendar), - "related_object": task, - }, + core.remove_task_calendar( + tool.Ifc, + tool.Sequence, + task=tool.Ifc.get().by_id(self.task), + work_calendar=tool.Ifc.get().by_id(self.work_calendar), ) - ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=task) - Data.load(IfcStore.get_file()) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} class EnableEditingTaskSequence(bpy.types.Operator): @@ -1571,10 +923,7 @@ class EnableEditingTaskSequence(bpy.types.Operator): task: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - props.active_task_id = self.task - props.editing_task_type = "SEQUENCE" - bpy.ops.bim.load_task_properties() + core.enable_editing_task_sequence(tool.Sequence, task=tool.Ifc.get().by_id(self.task)) return {"FINISHED"} @@ -1584,8 +933,7 @@ class DisableEditingTaskTime(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 - bpy.ops.bim.disable_editing_task() + core.disable_editing_task_time(tool.Sequence) return {"FINISHED"} @@ -1596,141 +944,67 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): sequence: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.props.active_sequence_id = self.sequence - self.props.editing_sequence_type = "ATTRIBUTES" - self.props.sequence_attributes.clear() - self.enable_editing_sequence_attributes() + core.enable_editing_sequence_attributes(tool.Sequence, rel_sequence=tool.Ifc.get().by_id(self.sequence)) return {"FINISHED"} - def enable_editing_sequence_attributes(self): - data = Data.sequences[self.sequence] - blenderbim.bim.helper.import_attributes("IfcRelSequence", self.props.sequence_attributes, data) - class EnableEditingSequenceTimeLag(bpy.types.Operator): - bl_idname = "bim.enable_editing_sequence_time_lag" + bl_idname = "bim.enable_editing_sequence_lag_time" bl_label = "Enable Editing Sequence Time Lag" bl_options = {"REGISTER", "UNDO"} sequence: bpy.props.IntProperty() lag_time: bpy.props.IntProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.props.active_sequence_id = self.sequence - self.props.editing_sequence_type = "TIME_LAG" - self.props.time_lag_attributes.clear() - self.enable_editing_attributes() + core.enable_editing_sequence_lag_time( + tool.Sequence, + rel_sequence=tool.Ifc.get().by_id(self.sequence), + lag_time=tool.Ifc.get().by_id(self.lag_time), + ) return {"FINISHED"} - def enable_editing_attributes(self): - data = Data.lag_times[self.lag_time] - blenderbim.bim.helper.import_attributes( - "IfcLagTime", self.props.time_lag_attributes, data, self.import_attributes - ) - def import_attributes(self, name, prop, data): - if name == "LagValue": - prop = self.props.time_lag_attributes.add() - prop.name = name - prop.is_null = data[name] is None - prop.is_optional = False - if isinstance(data[name], isodate.Duration): - prop.data_type = "string" - prop.string_value = ( - "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration") - ) - return True - else: - prop.data_type = "float" - prop.float_value = 0.0 if prop.is_null else data[name] - return True - - -class UnassignLagTime(bpy.types.Operator): +class UnassignLagTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_lag_time" bl_label = "Unassign Time Lag" bl_options = {"REGISTER", "UNDO"} sequence: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.unassign_lag_time", self.file, **{"rel_sequence": self.file.by_id(self.sequence)} - ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.unassign_lag_time(tool.Ifc, tool.Sequence, rel_sequence=tool.Ifc.get().by_id(self.sequence)) -class AssignLagTime(bpy.types.Operator): +class AssignLagTime(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_lag_time" bl_label = "Assign Time Lag" bl_options = {"REGISTER", "UNDO"} sequence: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.assign_lag_time", - self.file, - **{"rel_sequence": self.file.by_id(self.sequence), "lag_value": "P0D"}, - ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.assign_lag_time(tool.Ifc, rel_sequence=tool.Ifc.get().by_id(self.sequence)) -class EditSequenceAttributes(bpy.types.Operator): +class EditSequenceAttributes(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_sequence_attributes" bl_label = "Edit Sequence" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - attributes = blenderbim.bim.helper.export_attributes(props.sequence_attributes) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.edit_sequence", - self.file, - **{"rel_sequence": self.file.by_id(props.active_sequence_id), "attributes": attributes}, + core.edit_sequence_attributes( + tool.Ifc, + tool.Sequence, + rel_sequence=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_sequence_id), ) - Data.load(self.file) - bpy.ops.bim.disable_editing_sequence() - bpy.ops.bim.load_task_properties() - return {"FINISHED"} -class EditSequenceTimeLag(bpy.types.Operator): - bl_idname = "bim.edit_sequence_time_lag" +class EditSequenceTimeLag(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.edit_sequence_lag_time" bl_label = "Edit Time Lag" bl_options = {"REGISTER", "UNDO"} lag_time: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - props = context.scene.BIMWorkScheduleProperties - attributes = blenderbim.bim.helper.export_attributes(props.time_lag_attributes) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.edit_lag_time", - self.file, - **{"lag_time": self.file.by_id(self.lag_time), "attributes": attributes}, - ) - Data.load(self.file) - bpy.ops.bim.disable_editing_sequence() - bpy.ops.bim.load_task_properties() - return {"FINISHED"} + core.edit_sequence_lag_time(tool.Ifc, tool.Sequence, lag_time=tool.Ifc.get().by_id(self.lag_time)) class DisableEditingSequence(bpy.types.Operator): @@ -1739,29 +1013,28 @@ class DisableEditingSequence(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMWorkScheduleProperties.active_sequence_id = 0 + core.disable_editing_rel_sequence(tool.Sequence) return {"FINISHED"} -class SelectTaskRelatedProducts(bpy.types.Operator): +class SelectTaskRelatedProducts(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.select_task_related_products" - bl_label = "Select Similar Type" + bl_label = "Select All Output Products" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + def _execute(self, context): + core.select_task_outputs(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) + + +class SelectTaskRelatedInputs(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.select_task_related_inputs" + bl_label = "Select All Input Products" + bl_options = {"REGISTER", "UNDO"} + task: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - related_products = ifcopenshell.api.run( - "sequence.get_related_products", self.file, **{"related_object": self.file.by_id(self.task)} - ) - for obj in context.visible_objects: - obj.select_set(False) - if obj.BIMObjectProperties.ifc_definition_id in related_products: - obj.select_set(True) - return {"FINISHED"} + core.select_task_inputs(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) class VisualiseWorkScheduleDate(bpy.types.Operator): @@ -1805,8 +1078,8 @@ class VisualiseWorkScheduleDate(bpy.types.Operator): for rel in task.IsNestedBy or []: for related_object in rel.RelatedObjects: self.preprocess_task(related_object) - start = helper.derive_date(task.id(), "ScheduleStart", is_earliest=True) - finish = helper.derive_date(task.id(), "ScheduleFinish", is_latest=True) + start = helper.derive_date(task, "ScheduleStart", is_earliest=True) + finish = helper.derive_date(task, "ScheduleFinish", is_latest=True) if not start or not finish: return products = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")] @@ -2026,10 +1299,12 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): for rel in task.IsNestedBy or []: for related_object in rel.RelatedObjects: self.preprocess_task(related_object) - start = helper.derive_date(task.id(), "ScheduleStart", is_earliest=True) - finish = helper.derive_date(task.id(), "ScheduleFinish", is_latest=True) + start = helper.derive_date(task, "ScheduleStart", is_earliest=True) + finish = helper.derive_date(task, "ScheduleFinish", is_latest=True) if not start or not finish: return + if not Data.is_loaded: + Data.load(self.file) # TO DO: REFACTOR OPERATOR for output_id in Data.tasks[task.id()]["Outputs"]: self.add_product_frame(output_id, task, start, finish, "output") for input_id in Data.tasks[task.id()]["Inputs"]: @@ -2149,22 +1424,14 @@ class BlenderBIM_RedrawDatePicker(bpy.types.Operator): return {"FINISHED"} -class RecalculateSchedule(bpy.types.Operator): +class RecalculateSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.recalculate_schedule" bl_label = "Recalculate Schedule" bl_options = {"REGISTER", "UNDO"} work_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "sequence.recalculate_schedule", self.file, work_schedule=self.file.by_id(self.work_schedule) - ) - Data.load(self.file) - return {"FINISHED"} + core.recalculate_schedule(tool.Ifc, work_schedule=tool.Ifc.get().by_id(self.work_schedule)) class AddTaskColumn(bpy.types.Operator): @@ -2176,10 +1443,7 @@ class AddTaskColumn(bpy.types.Operator): data_type: bpy.props.StringProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - new = self.props.columns.add() - new.name = f"{self.column_type}.{self.name}" - new.data_type = self.data_type + core.add_task_column(tool.Sequence, self.column_type, self.name, self.data_type) return {"FINISHED"} @@ -2190,8 +1454,7 @@ class RemoveTaskColumn(bpy.types.Operator): name: bpy.props.StringProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.props.columns.remove(self.props.columns.find(self.name)) + core.remove_task_column(tool.Sequence, self.name) return {"FINISHED"} @@ -2202,9 +1465,7 @@ class SetTaskSortColumn(bpy.types.Operator): column: bpy.props.StringProperty() def execute(self, context): - self.props = context.scene.BIMWorkScheduleProperties - self.props.sort_column = self.column - bpy.ops.bim.enable_editing_tasks(work_schedule=self.props.active_work_schedule_id) + core.set_task_sort_column(tool.Sequence, self.column) return {"FINISHED"} @@ -2214,16 +1475,7 @@ class LoadTaskResources(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - self.file = IfcStore.get_file() - self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties - ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id - self.props.task_resources.clear() - for resource_id in Data.tasks[ifc_definition_id]["Resources"]: - resource = self.file.by_id(resource_id) - new = self.props.task_resources.add() - new.ifc_definition_id = resource_id - new.name = resource.Name or "Unnamed" + core.load_task_resources(tool.Sequence) return {"FINISHED"} @@ -2233,16 +1485,7 @@ class LoadTaskInputs(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - self.file = IfcStore.get_file() - self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties - ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id - self.props.task_inputs.clear() - for input_id in Data.tasks[ifc_definition_id]["Inputs"]: - product = self.file.by_id(input_id) - new = self.props.task_inputs.add() - new.ifc_definition_id = input_id - new.name = product.Name or "Unnamed" + core.load_task_inputs(tool.Sequence) return {"FINISHED"} @@ -2252,29 +1495,15 @@ class LoadTaskOutputs(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - self.file = IfcStore.get_file() - self.props = context.scene.BIMWorkScheduleProperties - self.tprops = context.scene.BIMTaskTreeProperties - ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id - self.props.task_outputs.clear() - for output_id in Data.tasks[ifc_definition_id]["Outputs"]: - product = self.file.by_id(output_id) - new = self.props.task_outputs.add() - new.ifc_definition_id = output_id - new.name = product.Name or "Unnamed" + core.load_task_outputs(tool.Sequence) return {"FINISHED"} -class CalculateTaskDuration(bpy.types.Operator): +class CalculateTaskDuration(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.calculate_task_duration" bl_label = "Calculate Task Duration" bl_options = {"REGISTER", "UNDO"} task: bpy.props.IntProperty() - def execute(self, context): - props = context.scene.BIMWorkScheduleProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=self.file.by_id(self.task)) - Data.load(self.file) - bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) - return {"FINISHED"} + def _execute(self, context): + core.calculate_task_duration(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task)) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 5c3eb6a18f..a0d1e7f188 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -21,8 +21,9 @@ import isodate import ifcopenshell.api import ifcopenshell.util.attribute from blenderbim.bim.ifc import IfcStore -from ifcopenshell.api.sequence.data import Data from ifcopenshell.api.resource.data import Data as ResourceData +from blenderbim.bim.module.sequence.data import SequenceData +import blenderbim.bim.module.pset.data from blenderbim.bim.prop import StrProperty, Attribute from dateutil import parser from bpy.types import PropertyGroup @@ -78,17 +79,18 @@ def getTaskTimeColumns(self, context): def getWorkSchedules(self, context): - return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()] + return [(str(k), v["Name"], "") for k, v in SequenceData.data["work_schedules"].items()] def getWorkCalendars(self, context): - return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()] + return [(str(k), v["Name"], "") for k, v in SequenceData.data["work_calendars"].items()] def update_active_task_index(self, context): bpy.ops.bim.load_task_inputs() bpy.ops.bim.load_task_resources() bpy.ops.bim.load_task_outputs() + blenderbim.bim.module.pset.data.refresh() def updateTaskName(self, context): @@ -101,7 +103,7 @@ def updateTaskName(self, context): self.file, **{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}}, ) - Data.load(IfcStore.get_file()) + SequenceData.load() if props.active_task_id == self.ifc_definition_id: attribute = props.task_attributes.get("Name") attribute.string_value = self.name @@ -117,7 +119,7 @@ def updateTaskIdentification(self, context): self.file, **{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}}, ) - Data.load(self.file) + SequenceData.load() if props.active_task_id == self.ifc_definition_id: attribute = props.task_attributes.get("Identification") attribute.string_value = self.identification @@ -163,10 +165,10 @@ def updateTaskTimeDateTime(self, context, startfinish): task_time = task.TaskTime else: task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task) - Data.load(IfcStore.get_file()) + SequenceData.load() startfinish_key = "Schedule" + startfinish.capitalize() - if Data.task_times[task_time.id()][startfinish_key] == startfinish_datetime: + if SequenceData.data["task_times"][task_time.id()][startfinish_key] == startfinish_datetime: canonical_startfinish_value = canonicalise_time(startfinish_datetime) if startfinish_value != canonical_startfinish_value: setattr(self, startfinish, canonical_startfinish_value) @@ -177,7 +179,7 @@ def updateTaskTimeDateTime(self, context, startfinish): self.file, **{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}}, ) - Data.load(IfcStore.get_file()) + SequenceData.load() bpy.ops.bim.load_task_properties() @@ -201,13 +203,13 @@ def updateTaskDuration(self, context): task_time = task.TaskTime else: task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task) - Data.load(IfcStore.get_file()) + SequenceData.load() ifcopenshell.api.run( "sequence.edit_task_time", self.file, **{"task_time": task_time, "attributes": {"ScheduleDuration": self.duration}}, ) - Data.load(IfcStore.get_file()) + SequenceData.load() if props.active_task_id == self.ifc_definition_id: attribute = props.task_time_attributes.get("Duration") if attribute: @@ -285,6 +287,7 @@ class BIMWorkPlanProperties(PropertyGroup): active_work_plan_index: IntProperty(name="Active Work Plan Index") active_work_plan_id: IntProperty(name="Active Work Plan Id") work_schedules: EnumProperty(items=getWorkSchedules, name="Work Schedules") + show_work_plan_hints: BoolProperty(name="Show Hints", default=False) class BIMWorkScheduleProperties(PropertyGroup): @@ -326,7 +329,7 @@ class BIMWorkScheduleProperties(PropertyGroup): editing_sequence_type: StringProperty(name="Editing Sequence Type") active_sequence_id: IntProperty(name="Active Sequence Id") sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute) - time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute) + lag_time_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute) visualisation_start: StringProperty(name="Visualisation Start", update=update_visualisation_start) visualisation_finish: StringProperty(name="Visualisation Finish", update=update_visualisation_finish) speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000) @@ -349,6 +352,10 @@ class BIMWorkScheduleProperties(PropertyGroup): task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct) active_task_output_index: IntProperty(name="Active Task Output Index") +class BIMDuration(PropertyGroup): + duration_days: IntProperty(name="Days ") + duration_hours: IntProperty(name="Hours") + duration_minutes: IntProperty(name="Minutes") class BIMTaskTreeProperties(PropertyGroup): # This belongs by itself for performance reasons. https://developer.blender.org/T87737 diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index dbe68ca97e..5f5957d5fe 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -21,11 +21,8 @@ import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes -from ifcopenshell.api.sequence.data import Data -from ifcopenshell.api.resource.data import Data as ResourceData -import blenderbim.bim.module.sequence.helper as helper from blenderbim.bim.module.sequence.data import SequenceData -from datetime import datetime +from ifcopenshell.api.resource.data import Data as ResourceData class BIM_PT_work_plans(Panel): @@ -48,13 +45,15 @@ class BIM_PT_work_plans(Panel): self.props = context.scene.BIMWorkPlanProperties row = self.layout.row() - row.label( - text="{} Work Plans Found".format(SequenceData.number_of_work_plans_loaded), - icon="TEXT", - ) + if SequenceData.data["has_work_plans"]: + row.label( + text="{} Work Plans Found".format(SequenceData.data["number_of_work_plans_loaded"]), + icon="TEXT", + ) + else: + row.label(text="No Work Plans found.", icon="TEXT") row.operator("bim.add_work_plan", icon="ADD", text="") - - for work_plan_id, work_plan in SequenceData.work_plans.items(): + for work_plan_id, work_plan in SequenceData.data["work_plans"].items(): self.draw_work_plan_ui(work_plan_id, work_plan) def draw_work_plan_ui(self, work_plan_id, work_plan): @@ -62,7 +61,8 @@ class BIM_PT_work_plans(Panel): row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT") if self.props.active_work_plan_id == work_plan_id: - row.operator("bim.edit_work_plan", text="", icon="CHECKMARK") + if self.props.editing_type == "ATTRIBUTES": + row.operator("bim.edit_work_plan", text="", icon="CHECKMARK") row.operator("bim.disable_editing_work_plan", text="", icon="CANCEL") elif self.props.active_work_plan_id: row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id @@ -83,19 +83,25 @@ class BIM_PT_work_plans(Panel): draw_attributes(self.props.work_plan_attributes, self.layout) def draw_work_schedule_ui(self): - row = self.layout.row(align=True) - row.prop(self.props, "work_schedules", text="") - op = row.operator("bim.assign_work_schedule", text="", icon="ADD") - op.work_plan = self.props.active_work_plan_id - op.work_schedule = int(self.props.work_schedules) + if not SequenceData.is_loaded: + SequenceData.load() - for work_schedule_id in Data.work_plans[self.props.active_work_plan_id]["IsDecomposedBy"]: - work_schedule = Data.work_schedules[work_schedule_id] + if SequenceData.data["has_work_schedules"]: row = self.layout.row(align=True) - row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON") - op = row.operator("bim.unassign_work_schedule", text="", icon="X") + row.prop(self.props, "work_schedules", text="") + op = row.operator("bim.assign_work_schedule", text="", icon="ADD") op.work_plan = self.props.active_work_plan_id op.work_schedule = int(self.props.work_schedules) + for work_schedule_id in SequenceData.data["work_plans"][self.props.active_work_plan_id]["IsDecomposedBy"]: + work_schedule = SequenceData.data["work_schedules"][work_schedule_id] + row = self.layout.row(align=True) + row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON") + op = row.operator("bim.unassign_work_schedule", text="", icon="X") + op.work_plan = self.props.active_work_plan_id + op.work_schedule = int(self.props.work_schedules) + else: + row = self.layout.row() + row.label(text="Must Create a WorkSchedule First. See Work Schedule Panel", icon="INFO") class BIM_PT_work_schedules(Panel): @@ -113,23 +119,29 @@ class BIM_PT_work_schedules(Panel): return file and hasattr(file, "schema") and file.schema != "IFC2X3" def draw(self, context): + if not SequenceData.is_loaded: + SequenceData.load() self.props = context.scene.BIMWorkScheduleProperties self.tprops = context.scene.BIMTaskTreeProperties - if not Data.is_loaded: - Data.load(IfcStore.get_file()) - row = self.layout.row() - row.operator("bim.add_work_schedule", icon="ADD") + if SequenceData.data["has_work_schedules"]: + row.label( + text="{} Work Schedules Found".format(SequenceData.data["number_of_work_schedules_loaded"]), + icon="TEXT", + ) + else: + row.label(text="No Work Schedules found.", icon="TEXT") + row.operator("bim.add_work_schedule", text="", icon="ADD") - for work_schedule_id, work_schedule in Data.work_schedules.items(): + 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 and self.props.active_work_schedule_id == work_schedule_id: + 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": @@ -139,23 +151,23 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.recalculate_schedule", text="", icon="FILE_REFRESH").work_schedule = work_schedule_id row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id row.operator("bim.disable_editing_work_schedule", text="", icon="CANCEL") - elif self.props.active_work_schedule_id: - row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id else: - row.operator("bim.enable_editing_tasks", text="", icon="ACTION").work_schedule = work_schedule_id + 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 if self.props.active_work_schedule_id == work_schedule_id: - if self.props.should_show_column_ui: - self.draw_column_ui() - if self.props.should_show_visualisation_ui: - self.draw_visualisation_ui() 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: + self.draw_column_ui() + if self.props.should_show_visualisation_ui: + self.draw_visualisation_ui() self.draw_editable_task_ui(work_schedule_id) def draw_task_operators(self): @@ -250,68 +262,70 @@ class BIM_PT_work_schedules(Panel): self.draw_editable_task_time_attributes_ui() def draw_editable_task_sequence_ui(self): - task = Data.tasks[self.props.active_task_id] + task = SequenceData.data["tasks"][self.props.active_task_id] row = self.layout.row() row.label(text="{} Predecessors".format(len(task["IsSuccessorFrom"])), icon="BACK") for sequence_id in task["IsSuccessorFrom"]: - self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatingProcess") + self.draw_editable_sequence_ui(SequenceData.data["sequences"][sequence_id], "RelatingProcess") row = self.layout.row() row.label(text="{} Successors".format(len(task["IsPredecessorTo"])), icon="FORWARD") for sequence_id in task["IsPredecessorTo"]: - self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatedProcess") + self.draw_editable_sequence_ui(SequenceData.data["sequences"][sequence_id], "RelatedProcess") def draw_editable_sequence_ui(self, sequence, process_type): - task = Data.tasks[sequence[process_type]] + task = SequenceData.data["tasks"][sequence[process_type]] row = self.layout.row(align=True) row.label(text=task["Identification"] or "XXX") row.label(text=task["Name"] or "Unnamed") row.label(text=sequence["SequenceType"] or "N/A") if sequence["TimeLag"]: row.operator("bim.unassign_lag_time", text="", icon="X").sequence = sequence["id"] - row.label(text=isodate.duration_isoformat(Data.lag_times[sequence["TimeLag"]]["LagValue"])) + row.label(text=isodate.duration_isoformat(SequenceData.data["lag_times"][sequence["TimeLag"]]["LagValue"])) else: - row.operator("bim.assign_lag_time", text="", icon="ADD").sequence = sequence["id"] - row.label(text="N/A") + row.operator("bim.assign_lag_time", text="Add Time Lag", icon="ADD").sequence = sequence["id"] if self.props.active_sequence_id == sequence["id"]: if self.props.editing_sequence_type == "ATTRIBUTES": row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK") row.operator("bim.disable_editing_sequence", text="", icon="CANCEL") self.draw_editable_sequence_attributes_ui() - elif self.props.editing_sequence_type == "TIME_LAG": - op = row.operator("bim.edit_sequence_time_lag", text="", icon="CHECKMARK") + elif self.props.editing_sequence_type == "LAG_TIME": + op = row.operator("bim.edit_sequence_lag_time", text="", icon="CHECKMARK") op.lag_time = sequence["TimeLag"] row.operator("bim.disable_editing_sequence", text="", icon="CANCEL") - self.draw_editable_sequence_time_lag_ui() + self.draw_editable_sequence_lag_time_ui() else: if sequence["TimeLag"]: - op = row.operator("bim.enable_editing_sequence_time_lag", text="", icon="CON_LOCKTRACK") + op = row.operator("bim.enable_editing_sequence_lag_time", text="Edit Time Lag", icon="CON_LOCKTRACK") op.sequence = sequence["id"] op.lag_time = sequence["TimeLag"] - op = row.operator("bim.enable_editing_sequence_attributes", text="", icon="GREASEPENCIL") + op = row.operator("bim.enable_editing_sequence_attributes", text="Edit Sequence", icon="GREASEPENCIL") op.sequence = sequence["id"] def draw_editable_sequence_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.sequence_attributes, self.layout) - def draw_editable_sequence_time_lag_ui(self): - blenderbim.bim.helper.draw_attributes(self.props.time_lag_attributes, self.layout) + def draw_editable_sequence_lag_time_ui(self): + blenderbim.bim.helper.draw_attributes(self.props.lag_time_attributes, self.layout) def draw_editable_task_calendar_ui(self): - task = Data.tasks[self.props.active_task_id] + task = SequenceData.data["tasks"][self.props.active_task_id] if task["HasAssignmentsWorkCalendar"]: row = self.layout.row(align=True) - calendar = Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]] + calendar = SequenceData.data["work_calendars"][task["HasAssignmentsWorkCalendar"][0]] row.label(text=calendar["Name"] or "Unnamed") op = row.operator("bim.remove_task_calendar", text="", icon="X") op.work_calendar = task["HasAssignmentsWorkCalendar"][0] op.task = self.props.active_task_id - else: + elif SequenceData.data["has_work_calendars"]: row = self.layout.row(align=True) row.prop(self.props, "work_calendars", text="") op = row.operator("bim.edit_task_calendar", text="", icon="ADD") op.work_calendar = int(self.props.work_calendars) op.task = self.props.active_task_id + else: + row = self.layout.row(align=True) + row.label(text="Must Create a Calendar First. See Work Calendar Panel", icon="INFO") def draw_editable_task_attributes_ui(self): blenderbim.bim.helper.draw_attributes( @@ -353,16 +367,22 @@ class BIM_PT_task_icom(Panel): row2 = col.row(align=True) row2.label(text="Inputs") + total_task_inputs = len(self.props.task_inputs) if context.selected_objects: op = row2.operator("bim.assign_process", icon="ADD", text="") op.task = task.ifc_definition_id op.related_object_type = "PRODUCT" - op.related_object = "" + if total_task_inputs: op = row2.operator("bim.unassign_process", icon="REMOVE", text="") op.task = task.ifc_definition_id op.related_object_type = "PRODUCT" - op.related_object = "" + if not context.selected_objects and self.props.active_task_input_index < total_task_inputs: + input_id = self.props.task_inputs[self.props.active_task_input_index].ifc_definition_id + op.related_object = input_id + + op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="") + op.task = task.ifc_definition_id row2 = col.row() row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index") @@ -376,14 +396,17 @@ class BIM_PT_task_icom(Panel): op = row2.operator("bim.calculate_task_duration", text="", icon="TEMP") op.task = task.ifc_definition_id - total_resources = len(context.scene.BIMResourceTreeProperties.resources) + resource_props = context.scene.BIMResourceProperties + resource_tprops = context.scene.BIMResourceTreeProperties + total_resources = len(resource_tprops.resources) if total_resources and context.scene.BIMResourceProperties.active_resource_index < total_resources: - op = row2.operator("bim.assign_process", icon="ADD", text="") - op.task = task.ifc_definition_id - op.related_object_type = "RESOURCE" - op.resource = context.scene.BIMResourceTreeProperties.resources[ - context.scene.BIMResourceProperties.active_resource_index - ].ifc_definition_id + resource_id = resource_tprops.resources[resource_props.active_resource_index].ifc_definition_id + ResourceData.load(IfcStore.get_file()) + resource = ResourceData.resources[resource_id] + if resource["type"] != "IfcCrewResource": + op = row2.operator("bim.assign_process", icon="ADD", text="") + op.task = task.ifc_definition_id + op.related_object_type = "RESOURCE" total_task_resources = len(self.props.task_resources) if total_task_resources and self.props.active_task_resource_index < total_task_resources: @@ -402,14 +425,17 @@ class BIM_PT_task_icom(Panel): row2 = col.row(align=True) row2.label(text="Outputs") + total_task_outputs = len(self.props.task_outputs) if context.selected_objects: op = row2.operator("bim.assign_product", icon="ADD", text="") op.task = task.ifc_definition_id - op.relating_product = "" + if total_task_outputs: op = row2.operator("bim.unassign_product", icon="REMOVE", text="") op.task = task.ifc_definition_id - op.relating_product = "" + if not context.selected_objects and self.props.active_task_output_index < total_task_outputs: + output_id = self.props.task_outputs[self.props.active_task_output_index].ifc_definition_id + op.relating_product = output_id op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="") op.task = task.ifc_definition_id @@ -558,14 +584,20 @@ class BIM_PT_work_calendars(Panel): return file and hasattr(file, "schema") and file.schema != "IFC2X3" def draw(self, context): - if not Data.is_loaded: - Data.load(IfcStore.get_file()) + if not SequenceData.is_loaded: + SequenceData.load() + self.props = context.scene.BIMWorkCalendarProperties - row = self.layout.row() - row.operator("bim.add_work_calendar", icon="ADD") - - for work_calendar_id, work_calendar in Data.work_calendars.items(): + if SequenceData.data["has_work_calendars"]: + row.label( + text="{} Work Calendars Found".format(SequenceData.data["number_of_work_calendars_loaded"]), + icon="TEXT", + ) + else: + row.label(text="No Work Calendars found.", icon="TEXT") + row.operator("bim.add_work_calendar", icon="ADD", text="") + for work_calendar_id, work_calendar in SequenceData.data["work_calendars"].items(): self.draw_work_calendar_ui(work_calendar_id, work_calendar) def draw_work_calendar_ui(self, work_calendar_id, work_calendar): @@ -600,10 +632,10 @@ class BIM_PT_work_calendars(Panel): op.time_type = "ExceptionTimes" for work_time_id in work_calendar["WorkingTimes"]: - self.draw_work_time_ui(Data.work_times[work_time_id], time_type="WorkingTimes") + self.draw_work_time_ui(SequenceData.data["work_times"][work_time_id], time_type="WorkingTimes") for work_time_id in work_calendar["ExceptionTimes"]: - self.draw_work_time_ui(Data.work_times[work_time_id], time_type="ExceptionTimes") + self.draw_work_time_ui(SequenceData.data["work_times"][work_time_id], time_type="ExceptionTimes") def draw_work_time_ui(self, work_time, time_type): row = self.layout.row(align=True) @@ -628,7 +660,9 @@ class BIM_PT_work_calendars(Panel): def draw_editable_work_time_ui(self, work_time): draw_attributes(self.props.work_time_attributes, self.layout) if work_time["RecurrencePattern"]: - self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]]) + self.draw_editable_recurrence_pattern_ui( + SequenceData.data["recurrence_patterns"][work_time["RecurrencePattern"]] + ) else: row = self.layout.row(align=True) row.prop(self.props, "recurrence_types", icon="RECOVER_LAST", text="") @@ -650,7 +684,7 @@ class BIM_PT_work_calendars(Panel): op.recurrence_pattern = recurrence_pattern["id"] for time_period_id in recurrence_pattern["TimePeriods"]: - time_period = Data.time_periods[time_period_id] + time_period = SequenceData.data["time_periods"][time_period_id] row = box.row(align=True) row.label(text="{} - {}".format(time_period["StartTime"], time_period["EndTime"]), icon="TIME") op = row.operator("bim.remove_time_period", text="", icon="X") diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index d214ba68fc..68e7329a6a 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -17,14 +17,12 @@ # along with BlenderBIM Add-on. If not, see . -def add_work_plan(ifc, sequence): - ifc.run("sequence.add_work_plan") - sequence.load_work_plans() +def add_work_plan(ifc): + return ifc.run("sequence.add_work_plan") -def remove_work_plan(ifc, sequence, work_plan=None): +def remove_work_plan(ifc, work_plan=None): ifc.run("sequence.remove_work_plan", work_plan=work_plan) - sequence.load_work_plans() def enable_editing_work_plan(sequence, work_plan=None): @@ -36,9 +34,419 @@ def disable_editing_work_plan(sequence): sequence.disable_editing_work_plan() -def edit_work_plan(ifc, sequence): - work_plan = sequence.get_current_ifc_work_plan() +def edit_work_plan(ifc, sequence, work_plan=None): attributes = sequence.get_work_plan_attributes() ifc.run("sequence.edit_work_plan", work_plan=work_plan, attributes=attributes) sequence.disable_editing_work_plan() - sequence.load_work_plans() + + +def edit_work_schedule(ifc, sequence, work_schedule=None): + attributes = sequence.get_work_schedule_attributes() + ifc.run("sequence.edit_work_schedule", **{"work_schedule": work_schedule, "attributes": attributes}) + sequence.disable_editing_work_schedule() + + +def enable_editing_work_plan_schedules(sequence, work_plan=None): + sequence.enable_editing_work_plan_schedules(work_plan) + + +def add_work_schedule(ifc): + return ifc.run("sequence.add_work_schedule") + + +def remove_work_schedule(ifc, work_schedule=None): + ifc.run("sequence.remove_work_schedule", **{"work_schedule": work_schedule}) + + +def assign_work_schedule(ifc, sequence, work_plan=None, work_schedule=None): + if work_schedule: + sequence.hide_work_plan_hints() + return ifc.run("aggregate.assign_object", **{"relating_object": work_plan, "product": work_schedule}) + else: + sequence.show_work_plan_hints() + + +def unassign_work_schedule(ifc, work_plan=None, work_schedule=None): + ifc.run("aggregate.unassign_object", **{"relating_object": work_plan, "product": work_schedule}) + + +def enable_editing_work_schedule(sequence, work_schedule=None): + sequence.load_work_schedule_attributes(work_schedule) + sequence.enable_editing_work_schedule(work_schedule) + + +def enable_editing_work_schedule_tasks(sequence, work_schedule=None): + sequence.enable_editing_work_schedule_tasks(work_schedule) + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def create_task_tree(sequence, work_schedule): + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def expand_task(sequence, task=None): + sequence.expand_task(task) + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def contract_task(sequence, task=None): + sequence.contract_task(task) + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def remove_task(ifc, sequence, task=None): + ifc.run("sequence.remove_task", **{"task": task}) + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + sequence.disable_selecting_deleted_task() + + +def load_task_properties(sequence): + sequence.load_task_properties() + + +def disable_editing_work_schedule(sequence): + sequence.disable_editing_work_schedule() + + +def add_summary_task(ifc, sequence, work_schedule=None): + ifc.run("sequence.add_task", **{"work_schedule": work_schedule}) + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def add_task(ifc, sequence, parent_task=None): + ifc.run("sequence.add_task", **{"parent_task": parent_task}) + work_schedule = sequence.get_active_work_schedule() + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() + + +def enable_editing_task(sequence, task=None): + sequence.load_task_attributes(task) + sequence.enable_editing_task(task) + + +def edit_task(ifc, sequence, task=None): + attributes = sequence.get_task_attributes() + ifc.run("sequence.edit_task", **{"task": task, "attributes": attributes}) + sequence.load_task_properties(task=task) + sequence.disable_editing_task() + + +def copy_task_attribute(ifc, sequence, attribute_name=None): + for task in sequence.get_checked_tasks(): + ifc.run( + "sequence.edit_task", + **{"task": task, "attributes": {attribute_name: sequence.get_task_attribute_value(attribute_name)}} + ) + sequence.load_task_properties(task) + + +def disable_editing_task(sequence): + sequence.disable_editing_task() + + +def enable_editing_task_time(ifc, sequence, task=None): + task_time = sequence.get_task_time(task) + if task_time is None: + task_time = ifc.run("sequence.add_task_time", **{"task": task}) + sequence.load_task_time_attributes(task_time) + sequence.enable_editing_task_time(task) + + +def edit_task_time(ifc, sequence, task_time=None): + attributes = sequence.get_task_time_attributes() + print(task_time) + ifc.run("sequence.edit_task_time", **{"task_time": task_time, "attributes": attributes}) # nasty loop goes on when calendar props are messed up + task = sequence.get_active_task() + sequence.load_task_properties(task=task) + sequence.disable_editing_task_time() + + +def assign_predecessor(ifc, sequence, task=None): + predecessor_task = sequence.get_active_task() + ifc.run("sequence.assign_sequence", **{"relating_process": task, "related_process": predecessor_task}) + sequence.load_task_properties() + + +def unassign_predecessor(ifc, sequence, task=None): + predecessor_task = sequence.get_active_task() + ifc.run("sequence.unassign_sequence", **{"relating_process": task, "related_process": predecessor_task}) + sequence.load_task_properties() + + +def assign_successor(ifc, sequence, task=None): + successor_task = sequence.get_active_task() + ifc.run("sequence.assign_sequence", **{"relating_process": successor_task, "related_process": task}) + sequence.load_task_properties() + + +def unassign_successor(ifc, sequence, task=None): + successor_task = sequence.get_active_task() + ifc.run("sequence.unassign_sequence", **{"relating_process": successor_task, "related_process": task}) + sequence.load_task_properties() + + +def assign_products(ifc, sequence, task=None, products=None): + if not products: + products = sequence.get_selected_products() + for product in products: + ifc.run("sequence.assign_product", **{"relating_product": product, "related_object": task}) + outputs = sequence.get_task_outputs(task) + sequence.load_task_outputs(outputs) + + +def unassign_products(ifc, sequence, task=None, products=None): + if not products: + products = sequence.get_selected_products() + for product in products: + ifc.run("sequence.unassign_product", **{"relating_product": product, "related_object": task}) + outputs = sequence.get_task_outputs(task) + sequence.load_task_outputs(outputs) + + +def assign_input_products(ifc, sequence, task=None, products=None): + if not products: + products = sequence.get_selected_products() + for product in products: + ifc.run("sequence.assign_process", **{"relating_process": task, "related_object": product}) + inputs = sequence.get_task_inputs(task) + sequence.load_task_inputs(inputs) + + +def unassign_input_products(ifc, sequence, task=None, products=None): + if not products: + products = sequence.get_selected_products() + for product in products: + ifc.run("sequence.unassign_process", **{"relating_process": task, "related_object": product}) + inputs = sequence.get_task_inputs(task) + sequence.load_task_inputs(inputs) + + +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} + ) + ifc.run("sequence.assign_process", **{"relating_process": task, "related_object": sub_resource}) + resources = sequence.get_task_resources(task) + sequence.load_task_resources(resources) + sequence.load_resources() + + +def unassign_resource(ifc, sequence, task=None, resource=None): + ifc.run("sequence.unassign_process", **{"relating_process": task, "related_object": resource}) + ifc.run("resource.remove_resource", **{"resource": resource}) + resources = sequence.get_task_resources(task) + sequence.load_task_resources(resources) + sequence.load_resources() + + +def load_task_outputs(sequence): + task = sequence.get_highlighted_task() + outputs = sequence.get_task_outputs(task) + sequence.load_task_outputs(outputs) + + +def load_task_inputs(sequence): + task = sequence.get_highlighted_task() + inputs = sequence.get_task_inputs(task) + sequence.load_task_inputs(inputs) + + +def load_task_resources(sequence): + task = sequence.get_highlighted_task() + resources = sequence.get_task_resources(task) + sequence.load_task_resources(resources) + + +def remove_work_calendar(ifc, work_calendar=None): + ifc.run("sequence.remove_work_calendar", **{"work_calendar": work_calendar}) + + +def add_work_calendar(ifc): + return ifc.run("sequence.add_work_calendar") + + +def edit_work_calendar(ifc, sequence, work_calendar=None): + attributes = sequence.get_work_calendar_attributes() + ifc.run("sequence.edit_work_calendar", **{"work_calendar": work_calendar, "attributes": attributes}) + sequence.disable_editing_work_calendar() + sequence.load_task_properties() + + +def enable_editing_work_calendar(sequence, work_calendar=None): + sequence.load_work_calendar_attributes(work_calendar) + sequence.enable_editing_work_calendar(work_calendar) + + +def disable_editing_work_calendar(sequence): + sequence.disable_editing_work_calendar() + + +def enable_editing_work_calendar_times(sequence, work_calendar=None): + sequence.enable_editing_work_calendar_times(work_calendar) + + +def add_work_time(ifc, work_calendar=None, time_type=None): + return ifc.run("sequence.add_work_time", **{"work_calendar": work_calendar, "time_type": time_type}) + + +def enable_editing_work_time(sequence, work_time=None): + sequence.load_work_time_attributes(work_time) + sequence.enable_editing_work_time(work_time) + + +def disable_editing_work_time(sequence): + sequence.disable_editing_work_time() + + +def remove_work_time(ifc, work_time=None): + ifc.run("sequence.remove_work_time", **{"work_time": work_time}) + + +def edit_work_time(ifc, sequence): + work_time = sequence.get_active_work_time() + ifc.run("sequence.edit_work_time", **{"work_time": work_time, "attributes": sequence.get_work_time_attributes()}) + recurrence_pattern = work_time.RecurrencePattern + if recurrence_pattern: + ifc.run( + "sequence.edit_recurrence_pattern", + **{ + "recurrence_pattern": recurrence_pattern, + "attributes": sequence.get_recurrence_pattern_attributes(recurrence_pattern), + } + ) + sequence.disable_editing_work_time() + + +def assign_recurrence_pattern(ifc, work_time=None, recurrence_type=None): + return ifc.run("sequence.assign_recurrence_pattern", **{"parent": work_time, "recurrence_type": recurrence_type}) + + +def unassign_recurrence_pattern(ifc, recurrence_pattern=None): + ifc.run("sequence.unassign_recurrence_pattern", **{"recurrence_pattern": recurrence_pattern}) + + +def add_time_period(ifc, sequence, recurrence_pattern=None): + start_time, end_time = sequence.get_recurrence_pattern_times() + ifc.run( + "sequence.add_time_period", + **{"recurrence_pattern": recurrence_pattern, "start_time": start_time, "end_time": end_time} + ) + sequence.reset_time_period() + + +def remove_time_period(ifc, time_period=None): + ifc.run("sequence.remove_time_period", **{"time_period": time_period}) + + +def enable_editing_task_calendar(sequence, task=None): + sequence.enable_editing_task_calendar(task) + + +def edit_task_calendar(ifc, sequence, task=None, work_calendar=None): + ifc.run("control.assign_control", **{"relating_control": work_calendar, "related_object": task}) + ifc.run("sequence.cascade_schedule", **{"task": task}) + sequence.load_task_properties() + + +def remove_task_calendar(ifc, sequence, task=None, work_calendar=None): + ifc.run("control.unassign_control", **{"relating_control": work_calendar, "related_object": task}) + ifc.run("sequence.cascade_schedule", **{"task": task}) + sequence.load_task_properties() + + +def enable_editing_task_sequence(sequence, task=None): + sequence.enable_editing_task_sequence(task) + sequence.load_task_properties() + + +def disable_editing_task_sequence(sequence, task=None): + sequence.enable_editing_task_sequence(task) + sequence.load_task_properties() + + +def disable_editing_task_time(sequence): + sequence.disable_editing_task_time() + + +def enable_editing_sequence_attributes(sequence, rel_sequence=None): + sequence.enable_editing_rel_sequence_attributes(rel_sequence) + sequence.load_rel_sequence_attributes(rel_sequence) + + +def enable_editing_sequence_lag_time(sequence, rel_sequence=None, lag_time=None): + sequence.load_lag_time_attributes(lag_time) + sequence.enable_editing_sequence_lag_time(rel_sequence) + + +def unassign_lag_time(ifc, sequence, rel_sequence=None): + ifc.run("sequence.unassign_lag_time", **{"rel_sequence": rel_sequence}) + sequence.load_task_properties() + + +def assign_lag_time(ifc, rel_sequence=None): + ifc.run("sequence.assign_lag_time", **{"rel_sequence": rel_sequence, "lag_value": "P1D"}) + + +def edit_sequence_attributes(ifc, sequence, rel_sequence=None): + attributes = sequence.get_rel_sequence_attributes() + ifc.run("sequence.edit_sequence", **{"rel_sequence": rel_sequence, "attributes": attributes}) + sequence.disable_editing_rel_sequence() + sequence.load_task_properties() + + +def edit_sequence_lag_time(ifc, sequence, lag_time=None): + attributes = sequence.get_lag_time_attributes() + ifc.run("sequence.edit_lag_time", **{"lag_time": lag_time, "attributes": attributes}) + sequence.disable_editing_rel_sequence() + sequence.load_task_properties() + + +def disable_editing_rel_sequence(sequence): + sequence.disable_editing_rel_sequence() + + +def select_task_outputs(ifc, sequence, task=None): + outputs = sequence.get_task_outputs(task) ## should this be from the api instead ? + sequence.select_task_products(outputs) + + +def select_task_inputs(ifc, sequence, task=None): + inputs = sequence.get_task_inputs(task) ## should this be from the api instead ? + print(inputs) + sequence.select_task_products(inputs) + + +def recalculate_schedule(ifc, work_schedule=None): + ifc.run("sequence.recalculate_schedule", **{"work_schedule": work_schedule}) + + +def add_task_column(sequence, column_type=None, name=None, data_type=None): + sequence.add_task_column(column_type, name, data_type) + + +def remove_task_column(sequence, name=None): + sequence.remove_task_column(name) + + +def set_task_sort_column(sequence, column=None): + sequence.set_task_sort_column(column) + + +def calculate_task_duration(ifc, sequence, task=None): + ifc.run("sequence.calculate_task_duration", **{"task": task}) + work_schedule = sequence.get_active_work_schedule() + if work_schedule: + sequence.create_task_tree(work_schedule) + sequence.load_task_properties() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 2081d61d48..f086674d5b 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -423,13 +423,79 @@ class Selector: @interface class Sequence: - def disable_editing_work_plan(cls): pass + def get_work_plan_attributes(cls): pass + def load_work_plan_attributes(cls, work_plan): pass + def get_nested_tasks(cls, task):pass def enable_editing_work_plan(cls, work_plan): pass - def export_attributes(cls): pass - def get_current_ifc_work_plan(cls): pass - def get_ifc_work_plan_attributes(cls): pass - def load_work_plan_attributes(cls): pass - def load_work_plans(cls): pass + def disable_editing_work_plan(cls): pass + def enable_editing_work_plan_schedules(cls, work_plan): pass + def get_work_schedule_attributes(cls): pass + def load_work_schedule_attributes(cls, work_schedule): pass + def enable_editing_work_schedule(cls,work_schedule): pass + def disable_editing_work_schedule(cls): pass + def enable_editing_work_schedule_tasks(cls, work_schedule): pass + def create_task_tree(cls, work_schedule): pass + def load_task_properties(cls, task): pass + def hide_work_plan_hints(cls): pass + def show_work_plan_hints(cls): pass + def get_active_work_schedule_id(cls): pass + def get_selected_resource(cls): pass + def expand_task(cls, task): pass + def contract_task(cls, task): pass + def disable_work_schedule(cls): pass + def disable_selecting_deleted_task(cls): pass + def get_checked_tasks(cls): pass + def get_task_attribute_value(cls, attribute_name): pass + def get_active_task(cls): pass + def get_task_time(cls, task): pass + def load_task_attributes(cls, task): pass + def get_selected_products(cls): pass + def enable_editing_task(cls, task): pass + def get_task_attributes(cls): pass + def load_task_time_attributes(cls, task_time): pass + def enable_editing_task_time(cls, task): pass + def disable_editing_task(cls): pass + def get_task_time_attributes(cls): pass + def load_task_resources(cls,resources): pass + def load_resources(cls): pass + def get_task_inputs(cls, task): pass + def load_task_inputs(cls, inputs): pass + def load_task_outputs(cls, outputs): pass + def get_highlighted_task(cls): pass + def get_task_outputs(cls, task): pass + def get_task_resources(cls, task):pass + def enable_editing_work_calendar_times(cls, work_calendar): pass + def load_work_calendar_attributes(cls, work_calendar): pass + def enable_editing_work_calendar(cls, work_calendar): pass + def disable_editing_work_calendar(cls): pass + def get_work_calendar_attributes(cls): pass + def load_work_time_attributes(cls, work_time): pass + def enable_editing_work_time(cls, work_time): pass + def get_work_time_attributes(cls): pass + def get_recurrence_pattern_attributes(cls, recurrence_pattern): pass + def disable_editing_work_time(cls): pass + def get_recurrence_pattern_times(cls): pass + def reset_time_period(cls): pass + def enable_editing_task_calendar(cls, task): pass + def enable_editing_task_sequence(cls, task): pass + def disable_editing_task_time(cls): pass + def load_rel_sequence_attributes(cls, rel_sequence): pass + def enable_editing_rel_sequence_attributes(cls, rel_sequence): pass + def load_lag_time_attributes(cls, lag_time): pass + def enable_editing_sequence_lag_time(cls, rel_sequence): pass + def get_rel_sequence_attributes(cls): pass + def disable_editing_rel_sequence(cls): pass + def get_lag_time_attributes(cls): pass + def select_task_products(cls, products): pass + def add_task_column(cls, column_type, name, data_type): pass + def remove_task_column(cls, name): pass + def set_task_sort_column(cls, column): pass + def find_related_output_tasks(cls, column): pass + def get_root_task(cls, task): pass + def get_task_work_schedule(cls, task): pass + def is_work_schedule_active(cls, work_schedule): pass + def highlight_task(cls, task): pass + @interface diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 261812284d..62e6410c98 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -40,9 +40,9 @@ from blenderbim.tool.project import Project from blenderbim.tool.pset import Pset from blenderbim.tool.qto import Qto from blenderbim.tool.root import Root +from blenderbim.tool.sequence import Sequence from blenderbim.tool.spatial import Spatial from blenderbim.tool.structural import Structural -from blenderbim.tool.sequence import Sequence from blenderbim.tool.style import Style from blenderbim.tool.surveyor import Surveyor from blenderbim.tool.system import System diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index ac2560f4f1..2c27335196 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -15,8 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . - +from dateutil import parser import bpy +import re +import isodate +from datetime import datetime import ifcopenshell import json import blenderbim.core.tool @@ -26,40 +29,6 @@ import blenderbim.bim.module.sequence.helper as helper class Sequence(blenderbim.core.tool.Sequence): - @classmethod - def load_work_plans(cls): - props = bpy.context.scene.BIMWorkPlanProperties - props.work_plans.clear() - for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"): - new = props.work_plans.add() - new.ifc_definition_id = work_plan.id() - new.name = work_plan.Name or "Unnamed" - - @classmethod - def enable_editing_work_plan(cls, work_plan): - if work_plan: - bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id() - bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES" - - @classmethod - def disable_editing_work_plan(cls): - bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0 - - @classmethod - def get_current_ifc_work_plan(cls): - return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id) - - @classmethod - def load_work_plan_attributes(cls, work_plan): - def callback(name, prop, data): - if name in ["CreationDate", "StartTime", "FinishTime"]: - prop.string_value = "" if prop.is_null else data[name] - return True - - props = bpy.context.scene.BIMWorkPlanProperties - props.work_plan_attributes.clear() - blenderbim.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback) - @classmethod def get_work_plan_attributes(cls): def callback(attributes, prop): @@ -78,3 +47,708 @@ class Sequence(blenderbim.core.tool.Sequence): props = bpy.context.scene.BIMWorkPlanProperties return blenderbim.bim.helper.export_attributes(props.work_plan_attributes, callback) + + @classmethod + def load_work_plan_attributes(cls, work_plan): + def callback(name, prop, data): + if name in ["CreationDate", "StartTime", "FinishTime"]: + prop.string_value = "" if prop.is_null else data[name] + return True + + props = bpy.context.scene.BIMWorkPlanProperties + props.work_plan_attributes.clear() + blenderbim.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback) + + @classmethod + def enable_editing_work_plan(cls, work_plan): + if work_plan: + bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id() + bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES" + + @classmethod + def disable_editing_work_plan(cls): + bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0 + + @classmethod + def enable_editing_work_plan_schedules(cls, work_plan): + if work_plan: + bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id() + bpy.context.scene.BIMWorkPlanProperties.editing_type = "SCHEDULES" + + @classmethod + def get_work_schedule_attributes(cls): + def callback(attributes, prop): + if "Date" in prop.name or "Time" in prop.name: + if prop.is_null: + attributes[prop.name] = None + return True + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "Duration" or prop.name == "TotalFloat": + if prop.is_null: + attributes[prop.name] = None + return True + attributes[prop.name] = helper.parse_duration(prop.string_value) + return True + + props = bpy.context.scene.BIMWorkScheduleProperties + return blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, callback) + + @classmethod + def load_work_schedule_attributes(cls, work_schedule): + def callback(name, prop, data): + if name in ["CreationDate", "StartTime", "FinishTime"]: + prop.string_value = "" if prop.is_null else data[name] + return True + + props = bpy.context.scene.BIMWorkScheduleProperties + props.work_schedule_attributes.clear() + blenderbim.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback) + + @classmethod + def enable_editing_work_schedule(cls, work_schedule): + bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = work_schedule.id() + bpy.context.scene.BIMWorkScheduleProperties.editing_type = "WORK_SCHEDULE" + + @classmethod + def disable_editing_work_schedule(cls): + bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + + @classmethod + def enable_editing_work_schedule_tasks(cls, work_schedule): + if work_schedule: + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_work_schedule_id = work_schedule.id() + props.editing_type = "TASKS" + + @classmethod + def create_task_tree(cls, work_schedule): + def get_work_schedule_root_tasks(work_schedule): + related_objects_ids = [] + if work_schedule.Controls: + for rel in work_schedule.Controls: + for obj in rel.RelatedObjects: + if obj.is_a("IfcTask"): + related_objects_ids.append(obj.id()) + return related_objects_ids + + bpy.context.scene.BIMTaskTreeProperties.tasks.clear() + props = bpy.context.scene.BIMWorkScheduleProperties + cls.contracted_tasks = json.loads(props.contracted_tasks) + + related_objects_ids = get_work_schedule_root_tasks(work_schedule) + if not related_objects_ids: + return + cls.sort_keys = {i: cls.get_sort_key(tool.Ifc.get().by_id(i)) for i in related_objects_ids} + related_objects_ids = sorted(cls.sort_keys, key=cls.natural_sort_key) + if props.is_sort_reversed: + related_objects_ids.reverse() + for related_object_id in related_objects_ids: + cls.create_new_task_li(related_object_id, 0) + + @classmethod + def create_new_task_li(cls, related_object_id, level_index): + task = tool.Ifc.get().by_id(related_object_id) + new = bpy.context.scene.BIMTaskTreeProperties.tasks.add() + new.ifc_definition_id = related_object_id + new.is_expanded = related_object_id not in cls.contracted_tasks + new.level_index = level_index + if task.IsNestedBy: + new.has_children = True + if new.is_expanded: + cls.sort_keys = {subtask.id(): cls.get_sort_key(subtask) for subtask in helper.get_nested_tasks(task)} + related_object_ids = sorted(cls.sort_keys, key=cls.natural_sort_key) + if bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed: + related_object_ids.reverse() + for related_object_id in related_object_ids: + cls.create_new_task_li(related_object_id, level_index + 1) + + @classmethod + def natural_sort_key(cls, i, _nsre=re.compile("([0-9]+)")): + s = cls.sort_keys[i] + return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] + + @classmethod + def get_sort_key(cls, task): + # Sorting only applies to actual tasks, not the WBS + for rel in task.IsNestedBy: + for object in rel.RelatedObjects: + if object.is_a("IfcTask"): + return "0000000000" + (task.Identification or "") + if not bpy.context.scene.BIMWorkScheduleProperties.sort_column: + return task.Identification or "" + column_type, name = bpy.context.scene.BIMWorkScheduleProperties.sort_column.split(".") + if column_type == "IfcTask": + return task.Name or "" + elif column_type == "IfcTaskTime" and task.TaskTime: + return task.TaskTime.Name or "" + return task.Identification or "" + + @classmethod + def load_task_properties(cls, task=None): + def canonicalise_time(time): + if not time: + return "-" + return time.strftime("%d/%m/%y") + + props = bpy.context.scene.BIMWorkScheduleProperties + task_props = bpy.context.scene.BIMTaskTreeProperties + props.is_task_update_enabled = False + + for item in task_props.tasks: + # if task and item.ifc_definition_id != task.id(): + # continue + task = tool.Ifc.get().by_id(item.ifc_definition_id) + item.name = task.Name or "Unnamed" + item.identification = task.Identification or "XXX" + if props.active_task_id: + item.is_predecessor = props.active_task_id in [rel.RelatedProcess.id() for rel in task.IsPredecessorTo] + item.is_successor = props.active_task_id in [rel.RelatingProcess.id() for rel in task.IsSuccessorFrom] + + calendar = ifcopenshell.util.sequence.derive_calendar(task) + if task.HasAssignments: + for rel in task.HasAssignments: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar"): + item.calendar = calendar.Name if calendar else "" + else: + item.calendar = "" + item.derived_calendar = calendar.Name if calendar else "" + + if task.TaskTime: + task_time = task.TaskTime + item.start = ( + canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart)) + if task_time.ScheduleStart + else "-" + ) + item.finish = ( + canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleFinish)) + if task_time.ScheduleFinish + else "-" + ) + item.duration = ( + isodate.duration_isoformat(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration)) + if task_time.ScheduleDuration + else "-" + ) + else: + derived_start = helper.derive_date(task, "ScheduleStart", is_earliest=True) + derived_finish = helper.derive_date(task, "ScheduleFinish", is_latest=True) + item.derived_start = canonicalise_time(derived_start) if derived_start else "" + item.derived_finish = canonicalise_time(derived_finish) if derived_finish else "" + if derived_start and derived_finish and calendar: + derived_duration = ifcopenshell.util.sequence.count_working_days( + derived_start, derived_finish, calendar + ) + item.derived_duration = f"P{derived_duration}D" + item.start = "-" + item.finish = "-" + item.duration = "-" + + bpy.context.scene.BIMWorkScheduleProperties.is_task_update_enabled = True + + @classmethod + def hide_work_plan_hints(cls): + bpy.context.scene.BIMWorkPlanProperties.show_hints = False + + @classmethod + def show_work_plan_hints(cls): + bpy.context.scene.BIMWorkPlanProperties.show_hints = True + + @classmethod + def get_active_work_schedule(cls): + if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id: + return None + return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id) + + @classmethod + def get_selected_resource(cls): + if bpy.context.scene.BIMResourceTreeProperties.resources: + selected_resource_id = bpy.context.scene.BIMResourceTreeProperties.resources[ + bpy.context.scene.BIMResourceProperties.active_resource_index + ].ifc_definition_id + return tool.Ifc.get().by_id(selected_resource_id) + + @classmethod + def expand_task(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + contracted_tasks = json.loads(props.contracted_tasks) + contracted_tasks.remove(task.id()) + props.contracted_tasks = json.dumps(contracted_tasks) + + @classmethod + def contract_task(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + contracted_tasks = json.loads(props.contracted_tasks) + contracted_tasks.append(task.id()) + props.contracted_tasks = json.dumps(contracted_tasks) + + @classmethod + def disable_work_schedule(cls): + bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0 + + @classmethod + def disable_selecting_deleted_task(cls): + props = bpy.context.scene.BIMWorkScheduleProperties + if props.active_task_id not in [ + task.ifc_definition_id for task in bpy.context.scene.BIMTaskTreeProperties.tasks + ]: # Task was deleted + bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 + bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + + @classmethod + def get_checked_tasks(cls): + return [ + tool.Ifc.get().by_id(task.ifc_definition_id) + for task in bpy.context.scene.BIMTaskTreeProperties.tasks + if task.is_selected + ] + + @classmethod + def get_task_attribute_value(cls, attribute_name): + return bpy.context.scene.BIMWorkScheduleProperties.task_attributes.get(attribute_name).get_value() + + @classmethod + def get_active_task(cls): + return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_task_id) + + @classmethod + def get_active_work_time(cls): + return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id) + + @classmethod + def get_task_time(cls, task): + return task.TaskTime if task.TaskTime else None + + @classmethod + def load_task_attributes(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + props.task_attributes.clear() + blenderbim.bim.helper.import_attributes2(task, props.task_attributes) + + @classmethod + def get_selected_products(cls): + return [ + tool.Ifc.get_entity(obj) + for obj in bpy.context.selected_objects + if obj.BIMObjectProperties.ifc_definition_id + ] or [] + + @classmethod + def enable_editing_task(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_task_id = task.id() + props.editing_task_type = "ATTRIBUTES" + + @classmethod + def get_task_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.task_attributes) + + @classmethod + def load_task_time_attributes(cls, task_time): + def callback(name, prop, data): + if prop.data_type == "string": + if name == "ScheduleDuration" and data[name] and isinstance(data[name], str): + time_object = ifcopenshell.util.date.ifc2datetime(data[name]) + minutes = int(round(time_object.seconds/60 % 60)) + hours = (time_object.seconds - minutes*60)/60/60 + bpy.context.scene.BIMDuration.duration_days = int(time_object.days) + bpy.context.scene.BIMDuration.duration_hours = round(hours) + bpy.context.scene.BIMDuration.duration_minutes = minutes # should consider years, months and seconds. + if isinstance(data[name], datetime): + prop.string_value = "" if prop.is_null else data[name].isoformat() + if name == "ScheduleDuration": + bpy.context.scene.BIMDuration.duration_days = data[name].days + bpy.context.scene.BIMDuration.duration_hours = round(data[name].seconds / 60 / 60) + bpy.context.scene.BIMDuration.duration_minutes = data[name].seconds/60 % 60 + return True + elif isinstance(data[name], isodate.Duration): + prop.string_value = ( + "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration") + ) + return True + + props = bpy.context.scene.BIMWorkScheduleProperties + props.task_time_attributes.clear() + blenderbim.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback) + + @classmethod + def enable_editing_task_time(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_task_id = task.id() + props.active_task_time_id = task.TaskTime.id() + props.editing_task_type = "TASKTIME" + + @classmethod + def disable_editing_task(cls): + bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 + bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + + @classmethod + def get_task_time_attributes(cls): + def callback(attributes, prop): + if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": + if prop.is_null: + attributes[prop.name] = None + return True + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "ScheduleDuration": + if prop.is_null: + return True + # TODO make this parse PT32 as P4D + attributes[prop.name] = helper.parse_duration(prop.string_value) + dprops= bpy.context.scene.BIMDuration + duration_days = dprops.duration_days if dprops.duration_days else 0 + duration_hours = dprops.duration_hours if dprops.duration_hours else 0 + duration_minutes = dprops.duration_minutes if dprops.duration_minutes else 0 + dprops.duration_days = 0 + dprops.duration_hours = 0 + dprops.duration_minutes = 0 + attributes[prop.name] = helper.parse_duration(f"P{duration_days}DT{duration_hours}H{duration_minutes}M") + return True + + props = bpy.context.scene.BIMWorkScheduleProperties + return blenderbim.bim.helper.export_attributes(props.task_time_attributes, callback) + + @classmethod + def load_task_resources(cls, resources): + props = bpy.context.scene.BIMWorkScheduleProperties + props.task_resources.clear() + if resources: + for resource in resources: + new = props.task_resources.add() + new.ifc_definition_id = resource.id() + new.name = resource.Name or "Unnamed" + + @classmethod + def load_resources(cls): + bpy.ops.bim.load_resources() # remove and refactor + + @classmethod + def get_task_inputs(cls, task): + inputs = [] + for rel in task.OperatesOn: + for object in rel.RelatedObjects: + if object.is_a("IfcProduct"): + inputs.append(object) + return inputs + + @classmethod + def load_task_inputs(cls, inputs): + props = bpy.context.scene.BIMWorkScheduleProperties + props.task_inputs.clear() + for input in inputs: + new = props.task_inputs.add() + new.ifc_definition_id = input.id() + new.name = input.Name or "Unnamed" + + @classmethod + def load_task_outputs(cls, outputs): + props = bpy.context.scene.BIMWorkScheduleProperties + props.task_outputs.clear() + if outputs: + for output in outputs: + new = props.task_outputs.add() + new.ifc_definition_id = output.id() + new.name = output.Name or "Unnamed" + + @classmethod + def get_highlighted_task(cls): + props = bpy.context.scene.BIMWorkScheduleProperties + task_props = bpy.context.scene.BIMTaskTreeProperties + return tool.Ifc.get().by_id(task_props.tasks[props.active_task_index].ifc_definition_id) + + @classmethod + def get_task_outputs(cls, task): + return [rel.RelatingProduct for rel in task.HasAssignments if rel.is_a("IfcRelAssignsToProduct")] + + @classmethod + def get_task_resources(cls, task): + resources = [] + for rel in task.OperatesOn: + for object in rel.RelatedObjects: + if object.is_a("IfcResource"): + resources.append(object) + return resources + + @classmethod + def enable_editing_work_calendar_times(cls, work_calendar): + props = bpy.context.scene.BIMWorkCalendarProperties + props.active_work_calendar_id = work_calendar.id() + props.editing_type = "WORKTIMES" + + @classmethod + def load_work_calendar_attributes(cls, work_calendar): + props = bpy.context.scene.BIMWorkCalendarProperties + props.work_calendar_attributes.clear() + return blenderbim.bim.helper.import_attributes2(work_calendar, props.work_calendar_attributes) + + @classmethod + def enable_editing_work_calendar(cls, work_calendar): + bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = work_calendar.id() + bpy.context.scene.BIMWorkCalendarProperties.editing_type = "ATTRIBUTES" + + @classmethod + def disable_editing_work_calendar(cls): + bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0 + + @classmethod + def get_work_calendar_attributes(cls): + return blenderbim.bim.helper.export_attributes( + bpy.context.scene.BIMWorkCalendarProperties.work_calendar_attributes + ) + + @classmethod + def load_work_time_attributes(cls, work_time): + def callback(name, prop, data): + if name in ["Start", "Finish"]: + prop.string_value = "" if prop.is_null else data[name] + return True + + props = bpy.context.scene.BIMWorkCalendarProperties + props.work_time_attributes.clear() + + blenderbim.bim.helper.import_attributes2(work_time, props.work_time_attributes, callback) + + @classmethod + def enable_editing_work_time(cls, work_time): + def initialise_recurrence_components(props): + if len(props.day_components) == 0: + for i in range(0, 31): + new = props.day_components.add() + new.name = str(i + 1) + if len(props.weekday_components) == 0: + for d in ["M", "T", "W", "T", "F", "S", "S"]: + new = props.weekday_components.add() + new.name = d + if len(props.month_components) == 0: + for m in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]: + new = props.month_components.add() + new.name = m + + def load_recurrence_pattern_data(work_time, props): + props.position = 0 + props.interval = 0 + props.occurrences = 0 + props.start_time = "" + props.end_time = "" + for component in props.day_components: + component.is_specified = False + for component in props.weekday_components: + component.is_specified = False + for component in props.month_components: + component.is_specified = False + if not work_time.RecurrencePattern: + return + recurrence_pattern = work_time.RecurrencePattern + for attribute in ["Position", "Interval", "Occurrences"]: + if getattr(recurrence_pattern, attribute): + setattr(props, attribute.lower(), getattr(recurrence_pattern, attribute)) + for component in recurrence_pattern.DayComponent or []: + props.day_components[component - 1].is_specified = True + for component in recurrence_pattern.WeekdayComponent or []: + props.weekday_components[component - 1].is_specified = True + for component in recurrence_pattern.MonthComponent or []: + props.month_components[component - 1].is_specified = True + + props = bpy.context.scene.BIMWorkCalendarProperties + initialise_recurrence_components(props) + load_recurrence_pattern_data(work_time, props) + props.active_work_time_id = work_time.id() + props.editing_type = "WORKTIMES" + + @classmethod + def get_work_time_attributes(cls): + def callback(attributes, prop): + if "Start" in prop.name or "Finish" in prop.name: + if prop.is_null: + attributes[prop.name] = None + return True + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + + props = bpy.context.scene.BIMWorkCalendarProperties + return blenderbim.bim.helper.export_attributes(props.work_time_attributes, callback) + + @classmethod + def get_recurrence_pattern_attributes(cls, recurrence_pattern): + props = bpy.context.scene.BIMWorkCalendarProperties + attributes = { + "Interval": props.interval if props.interval > 0 else None, + "Occurrences": props.occurrences if props.occurrences > 0 else None, + } + applicable_data = { + "DAILY": ["Interval", "Occurrences"], + "WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"], + "MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"], + "MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"], + "BY_DAY_COUNT": ["Interval", "Occurrences"], + "BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"], + "YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"], + "YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"], + } + if "Position" in applicable_data[recurrence_pattern.RecurrenceType]: + attributes["Position"] = props.position if props.position != 0 else None + if "DayComponent" in applicable_data[recurrence_pattern.RecurrenceType]: + attributes["DayComponent"] = [i + 1 for i, c in enumerate(props.day_components) if c.is_specified] + if "WeekdayComponent" in applicable_data[recurrence_pattern.RecurrenceType]: + attributes["WeekdayComponent"] = [i + 1 for i, c in enumerate(props.weekday_components) if c.is_specified] + if "MonthComponent" in applicable_data[recurrence_pattern.RecurrenceType]: + attributes["MonthComponent"] = [i + 1 for i, c in enumerate(props.month_components) if c.is_specified] + return attributes + + @classmethod + def disable_editing_work_time(cls): + bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id = 0 + + @classmethod + def get_recurrence_pattern_times(cls): + props = bpy.context.scene.BIMWorkCalendarProperties + try: + start_time = parser.parse(props.start_time) + end_time = parser.parse(props.end_time) + return start_time, end_time + except: + return # improve UI / refactor to add user hints + + @classmethod + def reset_time_period(cls): + bpy.context.scene.BIMWorkCalendarProperties.start_time = "" + bpy.context.scene.BIMWorkCalendarProperties.end_time = "" + + @classmethod + def enable_editing_task_calendar(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_task_id = task.id() + props.editing_task_type = "CALENDAR" + + @classmethod + def enable_editing_task_sequence(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_task_id = task.id() + props.editing_task_type = "SEQUENCE" + + @classmethod + def disable_editing_task_time(cls): + bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0 + bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0 + + @classmethod + def load_rel_sequence_attributes(cls, rel_sequence): + props = bpy.context.scene.BIMWorkScheduleProperties + props.sequence_attributes.clear() + blenderbim.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes) + + @classmethod + def enable_editing_rel_sequence_attributes(cls, rel_sequence): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_sequence_id = rel_sequence.id() + props.editing_sequence_type = "ATTRIBUTES" + + + @classmethod + def load_lag_time_attributes(cls, lag_time): + def callback(name, prop, data): + if name == "LagValue": + prop = bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes.add() + prop.name = name + prop.is_null = data[name] is None + prop.is_optional = False + prop.data_type = "string" + prop.string_value = ( + "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name].wrappedValue, "IfcDuration") + ) + return True + + props = bpy.context.scene.BIMWorkScheduleProperties + props.lag_time_attributes.clear() + blenderbim.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback) + + @classmethod + def enable_editing_sequence_lag_time(cls, rel_sequence): + props = bpy.context.scene.BIMWorkScheduleProperties + props.active_sequence_id = rel_sequence.id() + props.editing_sequence_type = "LAG_TIME" + + @classmethod + def get_rel_sequence_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.sequence_attributes) + + @classmethod + def disable_editing_rel_sequence(cls): + bpy.context.scene.BIMWorkScheduleProperties.active_sequence_id = 0 + + @classmethod + def get_lag_time_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes) + + @classmethod + def select_task_products(cls, products): + for obj in bpy.context.visible_objects: + obj.select_set(False) + if obj.BIMObjectProperties.ifc_definition_id in [product.id() for product in products]: + obj.select_set(True) + + @classmethod + def add_task_column(cls, column_type, name, data_type): + props = bpy.context.scene.BIMWorkScheduleProperties + new = props.columns.add() + new.name = f"{column_type}.{name}" + new.data_type = data_type + + @classmethod + def remove_task_column(cls, name): + props = bpy.context.scene.BIMWorkScheduleProperties + props.columns.remove(props.columns.find(name)) + + @classmethod + def set_task_sort_column(cls, column): + props = bpy.context.scene.BIMWorkScheduleProperties + props.sort_column = column + + @classmethod + def find_related_input_tasks(cls, object): + related_tasks = [] + for assignment in object.HasAssignments: + if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess.is_a("IfcTask"): + related_tasks.append(assignment.RelatingProcess) + return related_tasks + + @classmethod + def find_related_output_tasks(cls, object): + related_tasks = [] + for reference in object.ReferencedBy: + if reference.is_a("IfcRelAssignsToProduct") and reference.RelatedObjects[0].is_a("IfcTask"): + related_tasks.append(reference.RelatedObjects[0]) + return related_tasks + + @classmethod + def get_root_task(cls, task): + return ( + task + if not task.Nests or not task.Nests[0].RelatingObject.is_a("IfcTask") + else cls.get_root_task(task.Nests[0].RelatingObject) + ) + + @classmethod + def get_task_work_schedule(cls, task): + if task.HasAssignments: + for rel in task.HasAssignments: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): + return rel.RelatingControl + + @classmethod + def is_work_schedule_active(cls, work_schedule): + return ( + True if work_schedule.id() == bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id else False + ) + + @classmethod + def highlight_task(cls, task): + props = bpy.context.scene.BIMWorkScheduleProperties + task_props = bpy.context.scene.BIMTaskTreeProperties + task_id = task.id() + task_index = [task.ifc_definition_id for task in task_props.tasks].index(task_id) + if task_index: + props.active_task_index = task_index diff --git a/src/blenderbim/test/bim/feature/pset.feature b/src/blenderbim/test/bim/feature/pset.feature index 4101822d3b..4515e57e75 100644 --- a/src/blenderbim/test/bim/feature/pset.feature +++ b/src/blenderbim/test/bim/feature/pset.feature @@ -57,7 +57,7 @@ Scenario: Enable pset editing - work schedule Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "{ifc}.by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I set "scene.WorkSchedulePsetProperties.pset_name" to "Pset_WorkControlCommon" And I press "bim.add_pset(obj_type='WorkSchedule')" And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()" @@ -90,7 +90,7 @@ Scenario: Enable pset editing - task Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And I set "scene.TaskPsetProperties.qto_name" to "Qto_TaskBaseQuantities" And I press "bim.add_qto(obj_type='Task')" diff --git a/src/blenderbim/test/bim/feature/sequence.feature b/src/blenderbim/test/bim/feature/sequence.feature index f2f12e8a2d..ee3c42e7ce 100644 --- a/src/blenderbim/test/bim/feature/sequence.feature +++ b/src/blenderbim/test/bim/feature/sequence.feature @@ -4,13 +4,225 @@ Feature: Sequence Scenario: Add work plan Given an empty IFC project When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + Then nothing happens + +Scenario: Add Work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + Then nothing happens + +Scenario: Remove work plan + Given an empty IFC project + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + When I press "bim.remove_work_plan(work_plan={work_plan})" + Then nothing happens + +Scenario: Remove work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + When I press "bim.remove_work_schedule(work_schedule={work_schedule})" + Then nothing happens + +Scenario: Enable Editing Work Plan + Given an empty IFC project + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + When I press "bim.enable_editing_work_plan(work_plan={work_plan})" + Then nothing happens + +Scenario: Edit Work Plan + Given an empty IFC project + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan(work_plan={work_plan})" + And I set "scene.BIMWorkPlanProperties.work_plan_attributes.get('Name').string_value" to "FooPlan" + When I press "bim.edit_work_plan()" + Then nothing happens + +Scenario: Edit Work Schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule(work_schedule={work_schedule})" + And I set "scene.BIMWorkScheduleProperties.work_schedule_attributes.get('Name').string_value" to "FooSchedule" + When I press "bim.edit_work_schedule()" + Then nothing happens + +Scenario: Disable Editing Work Plan + Given an empty IFC project + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan_schedules(work_plan={work_plan})" + When I press "bim.disable_editing_work_plan" + Then nothing happens + +Scenario: Assign work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan_schedules(work_plan={work_plan})" + And I press "bim.assign_work_schedule(work_plan={work_plan}, work_schedule={work_schedule})" + Then nothing happens + +Scenario: Unassign work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan_schedules(work_plan={work_plan})" + And I press "bim.assign_work_schedule(work_plan={work_plan}, work_schedule={work_schedule})" + And I press "bim.unassign_work_schedule(work_plan={work_plan}, work_schedule={work_schedule})" + Then nothing happens + +Scenario: Delete assigned work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan_schedules(work_plan={work_plan})" + And I press "bim.assign_work_schedule(work_plan={work_plan}, work_schedule={work_schedule})" + When I press "bim.remove_work_schedule(work_schedule={work_schedule})" + Then nothing happens + +Scenario: Remove Assigned work schedule + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + When I press "bim.add_work_plan" + And the variable "work_plan" is "IfcStore.get_file().by_type('IfcWorkPlan')[0].id()" + And I press "bim.enable_editing_work_plan_schedules(work_plan={work_plan})" + When I press "bim.assign_work_schedule(work_plan={work_plan}, work_schedule={work_schedule})" + Then nothing happens + +Scenario: Add work calendar + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + Then nothing happens + +Scenario: Remove work calendar + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.remove_work_calendar(work_calendar={work_calendar})" + Then nothing happens + +Scenario: Edit work calendar attributes + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar(work_calendar={work_calendar})" + And I set "scene.BIMWorkCalendarProperties.work_calendar_attributes.get('Name').string_value" to "FooCalendar" + When I press "bim.edit_work_calendar()" + Then nothing happens + +Scenario: Enable editing calendar times + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + Then nothing happens + +Scenario: Add calendar Working Time + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="WorkingTimes")" + Then nothing happens + +Scenario: Add calendar Exception Time + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + And I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="ExceptionTimes")" + Then nothing happens + +Scenario: Enable editing Working Time Attributes + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="WorkingTimes")" + And the variable "work_time" is "IfcStore.get_file().by_type('IfcWorkTime')[0].id()" + When I press "bim.enable_editing_work_time(work_time={work_time})" + +Scenario: Edit Working Time Start and End Period + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type='WorkingTimes')" + And the variable "work_time" is "IfcStore.get_file().by_type('IfcWorkTime')[0].id()" + When I press "bim.enable_editing_work_time(work_time={work_time})" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Start').string_value" to "2021-01-01" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Finish').string_value" to "2022-01-01" + When I press "bim.edit_work_time()" + Then nothing happens + +Scenario: Add Working Time Period + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type='WorkingTimes')" + And the variable "work_time" is "IfcStore.get_file().by_type('IfcWorkTime')[0].id()" + When I press "bim.enable_editing_work_time(work_time={work_time})" + When I press "bim.assign_recurrence_pattern(work_time={work_time}, recurrence_type='DAILY')" + And the variable "recurrence_pattern" is "IfcStore.get_file().by_type('IfcRecurrencePattern')[0].id()" + And I set "scene.BIMWorkCalendarProperties.start_time" to "9AM" + And I set "scene.BIMWorkCalendarProperties.end_time" to "1PM" + When I press "bim.add_time_period(recurrence_pattern={recurrence_pattern})" + When I press "bim.edit_work_time()" + Then nothing happens + +Scenario: Edit Working Time start finish and time period + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type='WorkingTimes')" + And the variable "work_time" is "IfcStore.get_file().by_type('IfcWorkTime')[0].id()" + When I press "bim.enable_editing_work_time(work_time={work_time})" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Start').string_value" to "2021-01-01" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Finish').string_value" to "2022-01-01" + When I press "bim.assign_recurrence_pattern(work_time={work_time}, recurrence_type='DAILY')" + And the variable "recurrence_pattern" is "IfcStore.get_file().by_type('IfcRecurrencePattern')[0].id()" + And I set "scene.BIMWorkCalendarProperties.start_time" to "9AM" + And I set "scene.BIMWorkCalendarProperties.end_time" to "1PM" + When I press "bim.add_time_period(recurrence_pattern={recurrence_pattern})" + When I press "bim.edit_work_time()" + Then nothing happens + + +Scenario: Unassign Calendar to task + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + And I press "bim.enable_editing_task_calendar(task={task})" + And I press "bim.edit_task_calendar(work_calendar={work_calendar}, task={task})" + When I press "bim.remove_task_calendar(work_calendar={work_calendar}, task={task})" Then nothing happens Scenario: Enable editing task Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" When I press "bim.enable_editing_task(task={task})" @@ -20,7 +232,7 @@ Scenario: Copy task attribute Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" @@ -30,11 +242,86 @@ Scenario: Copy task attribute And I press "bim.copy_task_attribute(name='Description')" Then nothing happens +Scenario: Unassign task Successor + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + When I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + And I press "bim.enable_editing_task(task={nested_task_one})" + And I press "bim.assign_successor(task={nested_task_two})" + When I press "bim.unassign_successor(task={nested_task_two})" + Then nothing happens + +Scenario: Edit time Lag + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + When I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + And I press "bim.enable_editing_task(task={nested_task_one})" + When I press "bim.assign_successor(task={nested_task_two})" + And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" + When I press "bim.assign_lag_time(sequence={rel_sequence})" + And the variable "lag_time" is "IfcStore.get_file().by_type('IfcLagTime')[0].id()" + And I press "bim.enable_editing_sequence_lag_time(sequence={rel_sequence}, lag_time={lag_time})" + And I set "scene.BIMWorkScheduleProperties.lag_time_attributes.get('LagValue').string_value" to "P5D" + When I press "bim.edit_sequence_lag_time(lag_time={lag_time})" + Then nothing happens + +Scenario: Unassign time Lag + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + When I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + And I press "bim.enable_editing_task(task={nested_task_one})" + When I press "bim.assign_successor(task={nested_task_two})" + And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" + When I press "bim.assign_lag_time(sequence={rel_sequence})" + And the variable "lag_time" is "IfcStore.get_file().by_type('IfcLagTime')[0].id()" + When I press "bim.unassign_lag_time(sequence={rel_sequence})" + Then nothing happens + +Scenario: Edit Sequence Relationship + Given an empty IFC project + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + When I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + And I press "bim.enable_editing_task(task={nested_task_one})" + When I press "bim.assign_successor(task={nested_task_two})" + And the variable "rel_sequence" is "IfcStore.get_file().by_type('IfcRelSequence')[0].id()" + And I press "bim.enable_editing_sequence_attributes(sequence={rel_sequence})" + And I set "scene.BIMWorkScheduleProperties.sequence_attributes.get('SequenceType').enum_value" to "START_START" + When I press "bim.edit_sequence_attributes()" + Then nothing happens + Scenario: See the current frame date as text Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -50,7 +337,7 @@ Scenario: Animate the construction of a wall Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" And I press "bim.enable_editing_task(task={task})" @@ -65,7 +352,7 @@ Scenario: Animate the construction of a wall And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" And the object "IfcWall/Cube" is selected - And I press "bim.assign_product(task={task}, relating_product='')" + And I press "bim.assign_product(task={task})" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -86,7 +373,7 @@ Scenario: Animate the demolition of a wall Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" And I press "bim.enable_editing_task(task={task})" @@ -101,7 +388,7 @@ Scenario: Animate the demolition of a wall And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" And the object "IfcWall/Cube" is selected - And I press "bim.assign_product(task={task}, relating_product='')" + And I press "bim.assign_product(task={task}, relating_product=0)" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -125,7 +412,7 @@ Scenario: Animate the operation of a wall Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" And I press "bim.enable_editing_task(task={task})" @@ -140,7 +427,7 @@ Scenario: Animate the operation of a wall And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" And the object "IfcWall/Cube" is selected - And I press "bim.assign_product(task={task}, relating_product='')" + And I press "bim.assign_product(task={task}, relating_product=0)" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -158,7 +445,7 @@ Scenario: Animate the movement of a wall Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" And I press "bim.enable_editing_task(task={task})" @@ -174,13 +461,13 @@ Scenario: Animate the movement of a wall And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" And the object "IfcWall/ToObject" is selected - And I press "bim.assign_product(task={task}, relating_product='')" + And I press "bim.assign_product(task={task}, relating_product=0)" When I add a cube And I rename the object "Cube" to "FromObject" And the object "FromObject" is selected And I press "bim.assign_class" And the object "IfcWall/FromObject" is selected - And I press "bim.assign_process(task={task}, related_object_type='PRODUCT', related_object='')" + And I press "bim.assign_process(task={task}, related_object_type='PRODUCT', related_object=0)" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -212,7 +499,7 @@ Scenario: Animate the consumption of a wall Given an empty IFC project And I press "bim.add_work_schedule" And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" - And I press "bim.enable_editing_tasks(work_schedule={work_schedule})" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" And I press "bim.add_summary_task(work_schedule={work_schedule})" And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" And I press "bim.enable_editing_task_time(task={task})" @@ -224,7 +511,7 @@ Scenario: Animate the consumption of a wall And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" And the object "IfcWall/Cube" is selected - And I press "bim.assign_process(task={task}, related_object_type='PRODUCT', related_object='')" + And I press "bim.assign_process(task={task}, related_object_type='PRODUCT', related_object=0)" And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" @@ -243,3 +530,135 @@ Scenario: Animate the consumption of a wall Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]" Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" Then "scene.objects.get('IfcWall/Cube').hide_render" is "True" + + +Scenario: Generate Gantt Chart + Given an empty IFC project + And I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + And I press "bim.enable_editing_task(task={task})" + And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "CONSTRUCTION" + And I press "bim.edit_task" + And I press "bim.enable_editing_task_time(task={task})" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I press "bim.generate_gantt_chart(work_schedule={work_schedule})" + Then nothing happens + +Scenario: Edit task with calendar + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_three" is "IfcStore.get_file().by_type('IfcTask')[3].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="WorkingTimes")" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="ExceptionTimes")" + And I press "bim.disable_editing_work_calendar" + And I press "bim.enable_editing_task_sequence(task={nested_task_one})" + And I press "bim.assign_successor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_sequence(task={nested_task_three})" + And I press "bim.assign_predecessor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_time(task={nested_task_three})" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I press "bim.enable_editing_task_calendar(task={task})" + And I press "bim.edit_task_calendar(work_calendar={work_calendar}, task={task})" + And I press "bim.enable_editing_task_sequence(task={nested_task_one})" + And I press "bim.disable_editing_task()" + Then nothing happens + + +Scenario: Assign task calendar with Working Time + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_three" is "IfcStore.get_file().by_type('IfcTask')[3].id()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="WorkingTimes")" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="ExceptionTimes")" + And I press "bim.disable_editing_work_calendar" + And I press "bim.enable_editing_task_sequence(task={nested_task_one})" + And I press "bim.assign_successor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_sequence(task={nested_task_three})" + And I press "bim.assign_predecessor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_calendar(task={task})" + And I press "bim.edit_task_calendar(work_calendar={work_calendar}, task={nested_task_three})" + And I press "bim.disable_editing_task()" + And I press "bim.enable_editing_task_time(task={nested_task_three})" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06" + # And I press "bim.edit_task_time" + Then nothing happens + +Scenario: Assign task calendar with no working time + Given an empty IFC project + When I press "bim.add_work_calendar" + And the variable "work_calendar" is "IfcStore.get_file().by_type('IfcWorkCalendar')[0].id()" + When I press "bim.add_work_schedule" + And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()" + And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})" + And I press "bim.add_summary_task(work_schedule={work_schedule})" + And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_one" is "IfcStore.get_file().by_type('IfcTask')[1].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_two" is "IfcStore.get_file().by_type('IfcTask')[2].id()" + When I press "bim.add_task(task={task})" + And the variable "nested_task_three" is "IfcStore.get_file().by_type('IfcTask')[3].id()" + And I press "bim.enable_editing_task_sequence(task={nested_task_one})" + And I press "bim.assign_successor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_sequence(task={nested_task_three})" + And I press "bim.assign_predecessor(task={nested_task_two})" + And I press "bim.disable_editing_task" + And I press "bim.enable_editing_task_calendar(task={task})" + And I press "bim.edit_task_calendar(work_calendar={work_calendar}, task={nested_task_three})" + And I press "bim.disable_editing_task()" + When I press "bim.enable_editing_work_calendar_times(work_calendar={work_calendar})" + When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="WorkingTimes")" + And the variable "work_time" is "IfcStore.get_file().by_type('IfcWorkTime')[0].id()" + When I press "bim.enable_editing_work_time(work_time={work_time})" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Start').string_value" to "01-08-2021" + And I set "scene.BIMWorkCalendarProperties.work_time_attributes.get('Finish').string_value" to "01-08-2022" + When I press "bim.assign_recurrence_pattern(work_time={work_time}, recurrence_type='DAILY')" + And the variable "recurrence_pattern" is "IfcStore.get_file().by_type('IfcRecurrencePattern')[0].id()" + And I set "scene.BIMWorkCalendarProperties.start_time" to "9AM" + And I set "scene.BIMWorkCalendarProperties.end_time" to "1PM" + When I press "bim.add_time_period(recurrence_pattern={recurrence_pattern})" + And I press "bim.edit_work_time" + # When I press "bim.add_work_time(work_calendar={work_calendar}, time_type="ExceptionTimes")" + And I press "bim.disable_editing_work_calendar" + And I press "bim.enable_editing_task_time(task={nested_task_three})" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06" + And I press "bim.edit_task_time" + Then nothing happens \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py index 834a32661d..a3eda30f38 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py @@ -62,7 +62,7 @@ class Usecase: predecessor = rel.RelatingProcess predecessor_duration = ( ifcopenshell.util.date.ifc2datetime(predecessor.TaskTime.ScheduleDuration) - if predecessor.TaskTime.ScheduleDuration + if predecessor.TaskTime and predecessor.TaskTime.ScheduleDuration else datetime.timedelta() ) if rel.SequenceType == "FINISH_START": diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 4996460308..d13adbd0e5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -21,7 +21,6 @@ import ifcopenshell.util.date class Data: is_loaded = False - work_plans = {} work_schedules = {} work_calendars = {} work_times = {} @@ -35,7 +34,6 @@ class Data: @classmethod def purge(cls): cls.is_loaded = False - cls.work_plans = {} cls.work_schedules = {} cls.work_calendars = {} cls.work_times = {} @@ -51,7 +49,6 @@ class Data: cls._file = file if not cls._file: return - cls.load_work_plans() cls.load_work_schedules() cls.load_work_calendars() cls.load_work_times() @@ -63,23 +60,6 @@ class Data: cls.load_sequences() cls.is_loaded = True - @classmethod - def load_work_plans(cls): - cls.work_plans = {} - 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"]) - if data["FinishTime"]: - data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]) - data["IsDecomposedBy"] = [] - for rel in work_plan.IsDecomposedBy: - data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects]) - cls.work_plans[work_plan.id()] = data - @classmethod def load_work_schedules(cls): cls.work_schedules = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py index b20dacf50c..61467dba69 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py @@ -34,4 +34,11 @@ class Usecase: definition=self.settings["work_calendar"], relating_context=self.file.by_type("IfcContext")[0], ) + if self.settings["work_calendar"].Controls: + for rel in self.settings["work_calendar"].Controls: + for object in rel.RelatedObjects: + ifcopenshell.api.run( + "control.unassign_control", + self.file, + **{"relating_control": self.settings["work_calendar"], "related_object": object}) self.file.remove(self.settings["work_calendar"])