diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index a10ea33681..7782163b5c 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -16,15 +16,18 @@ classes = ( operator.EnableEditingWorkSchedule, operator.EnableEditingTasks, operator.DisableEditingWorkSchedule, - operator.DisableEditingSequenceAttributes, + operator.DisableEditingSequence, operator.EditSequenceAttributes, + operator.EditSequenceTimeLag, operator.EnableEditingSequenceAttributes, + operator.EnableEditingSequenceTimeLag, operator.AddWorkCalendar, operator.EditWorkCalendar, operator.EditWorkTime, operator.RemoveWorkCalendar, operator.RemoveWorkTime, operator.UnassignRecurrencePattern, + operator.UnassignLagTime, operator.RemoveTimePeriod, operator.EnableEditingWorkCalendar, operator.EnableEditingWorkTime, @@ -32,6 +35,7 @@ classes = ( operator.DisableEditingWorkCalendar, operator.DisableEditingWorkTime, operator.AddWorkTime, + operator.AssignLagTime, operator.AssignRecurrencePattern, operator.AddTimePeriod, operator.AddTask, diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 6b631bdb51..bff9392025 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -303,7 +303,9 @@ class LoadTaskProperties(bpy.types.Operator): 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 "-" + item.duration = ( + isodate.duration_isoformat(task_time["ScheduleDuration"]) if task_time["ScheduleDuration"] else "-" + ) else: item.start = "-" item.finish = "-" @@ -433,7 +435,11 @@ class EnableEditingTaskTime(bpy.types.Operator): if isinstance(data[attribute.name()], datetime): new.string_value = "" if new.is_null else data[attribute.name()].isoformat() elif isinstance(data[attribute.name()], isodate.Duration): - new.string_value = "" if new.is_null else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") + new.string_value = ( + "" + if new.is_null + else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") + ) else: new.string_value = "" if new.is_null else data[attribute.name()] elif data_type == "boolean": @@ -1190,6 +1196,7 @@ class DisableEditingTaskTime(bpy.types.Operator): bpy.ops.bim.disable_editing_task() return {"FINISHED"} + class EnableEditingSequenceAttributes(bpy.types.Operator): bl_idname = "bim.enable_editing_sequence_attributes" bl_label = "Enable Editing Sequence Attributes" @@ -1198,10 +1205,11 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkScheduleProperties self.props.active_sequence_id = self.sequence + self.props.editing_sequence_type = "ATTRIBUTES" while len(self.props.sequence_attributes) > 0: self.props.sequence_attributes.remove(0) self.enable_editing_sequence_attributes() - return {'FINISHED'} + return {"FINISHED"} def enable_editing_sequence_attributes(self): data = Data.sequences[self.sequence] @@ -1221,9 +1229,82 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): if data[attribute.name()]: new.enum_value = data[attribute.name()] + +class EnableEditingSequenceTimeLag(bpy.types.Operator): + bl_idname = "bim.enable_editing_sequence_time_lag" + bl_label = "Enable Editing Sequence Time Lag" + 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" + while len(self.props.time_lag_attributes) > 0: + self.props.time_lag_attributes.remove(0) + self.enable_editing_attributes() + return {"FINISHED"} + + def enable_editing_attributes(self): + data = Data.lag_times[self.lag_time] + for attribute in IfcStore.get_schema().declaration_by_name("IfcLagTime").all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = self.props.time_lag_attributes.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + new.is_optional = attribute.optional() + new.data_type = data_type + if attribute.name() == "LagValue": + if isinstance(data[attribute.name()], isodate.Duration): + new.data_type = "string" + new.string_value = ( + "" + if new.is_null + else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") + ) + else: + new.data_type = "float" + new.float_value = 0.0 if new.is_null else data[attribute.name()] + elif data_type == "string": + new.string_value = "" if new.is_null else data[attribute.name()] + elif data_type == "float": + new.float_value = 0.0 if new.is_null else data[attribute.name()] + elif data_type == "enum": + new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) + if data[attribute.name()]: + new.enum_value = data[attribute.name()] + + +class UnassignLagTime(bpy.types.Operator): + bl_idname = "bim.unassign_lag_time" + bl_label = "Unassign Time Lag" + sequence: bpy.props.IntProperty() + + 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()) + return {"FINISHED"} + + +class AssignLagTime(bpy.types.Operator): + bl_idname = "bim.assign_lag_time" + bl_label = "Assign Time Lag" + sequence: bpy.props.IntProperty() + + 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"} + + + class EditSequenceAttributes(bpy.types.Operator): bl_idname = "bim.edit_sequence_attributes" - bl_label = "Edit Work Schedule" + bl_label = "Edit Sequence" def execute(self, context): props = context.scene.BIMWorkScheduleProperties @@ -1243,13 +1324,42 @@ class EditSequenceAttributes(bpy.types.Operator): **{"rel_sequence": self.file.by_id(props.active_sequence_id), "attributes": attributes}, ) Data.load(self.file) - bpy.ops.bim.disable_editing_sequence_attributes() + bpy.ops.bim.disable_editing_sequence() + return {"FINISHED"} + + +class EditSequenceTimeLag(bpy.types.Operator): + bl_idname = "bim.edit_sequence_time_lag" + bl_label = "Edit Time Lag" + lag_time: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + attributes = {} + for attribute in props.time_lag_attributes: + if attribute.is_null: + attributes[attribute.name] = None + else: + if attribute.data_type == "string": + attributes[attribute.name] = attribute.string_value + elif attribute.data_type == "float": + attributes[attribute.name] = attribute.float_value + elif attribute.data_type == "enum": + attributes[attribute.name] = attribute.enum_value + 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() return {"FINISHED"} -class DisableEditingSequenceAttributes(bpy.types.Operator): - bl_idname = "bim.disable_editing_sequence_attributes" +class DisableEditingSequence(bpy.types.Operator): + bl_idname = "bim.disable_editing_sequence" bl_label = "Disable Editing Sequence Attributes" def execute(self, context): @@ -1265,9 +1375,7 @@ class SelectTaskRelatedProducts(bpy.types.Operator): 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)} + "sequence.get_related_products", self.file, **{"related_object": self.file.by_id(self.task)} ) for obj in bpy.context.visible_objects: obj.select_set(False) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index bcfe67a8e1..388d76ba2b 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -161,8 +161,10 @@ class BIMWorkScheduleProperties(PropertyGroup): task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute) contracted_tasks: StringProperty(name="Contracted Task Items", default="[]") is_task_update_enabled: BoolProperty(name="Is Task Update Enabled", default=True) + 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) class BIMTaskTreeProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 02c0a1b3bb..0593815338 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -1,3 +1,4 @@ +import isodate from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.sequence.data import Data @@ -172,12 +173,29 @@ class BIM_PT_work_schedules(Panel): row.label(text=task["Identification"] or "XXX") row.label(text=task["Name"] or "Unnamed") row.label(text=sequence["SequenceType"] or "N/A") - if self.props.active_sequence_id == sequence["id"]: - row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_sequence_attributes", text="", icon="X") - self.draw_editable_sequence_attributes_ui() + 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"])) else: - row.operator("bim.enable_editing_sequence_attributes", text="", icon="GREASEPENCIL").sequence = sequence["id"] + row.operator("bim.assign_lag_time", text="", icon="ADD").sequence = sequence["id"] + row.label(text="N/A") + 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="X") + 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") + op.lag_time = sequence["TimeLag"] + row.operator("bim.disable_editing_sequence", text="", icon="X") + self.draw_editable_sequence_time_lag_ui() + else: + if sequence["TimeLag"]: + op = row.operator("bim.enable_editing_sequence_time_lag", text="", icon="CON_LOCKTRACK") + op.sequence = sequence["id"] + op.lag_time = sequence["TimeLag"] + op = row.operator("bim.enable_editing_sequence_attributes", text="", icon="GREASEPENCIL") + op.sequence = sequence["id"] def draw_editable_sequence_attributes_ui(self): for attribute in self.props.sequence_attributes: @@ -195,6 +213,22 @@ class BIM_PT_work_schedules(Panel): if attribute.is_optional: row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + def draw_editable_sequence_time_lag_ui(self): + for attribute in self.props.time_lag_attributes: + row = self.layout.row(align=True) + if attribute.data_type == "string": + row.prop(attribute, "string_value", text=attribute.name) + elif attribute.data_type == "boolean": + row.prop(attribute, "bool_value", text=attribute.name) + elif attribute.data_type == "integer": + row.prop(attribute, "int_value", text=attribute.name) + elif attribute.data_type == "float": + row.prop(attribute, "float_value", text=attribute.name) + elif attribute.data_type == "enum": + row.prop(attribute, "enum_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + def draw_editable_task_calendar_ui(self): task = Data.tasks[self.props.active_task_id] if task["HasAssignmentsWorkCalendar"]: @@ -312,9 +346,13 @@ class BIM_UL_tasks(UIList): row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id else: row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = item.ifc_definition_id - row.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="").task = item.ifc_definition_id + row.operator( + "bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="" + ).task = item.ifc_definition_id row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id - row.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = item.ifc_definition_id + row.operator( + "bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO" + ).task = item.ifc_definition_id row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id @@ -382,9 +420,7 @@ class BIM_PT_work_calendars(Panel): def draw_work_time_ui(self, work_time, time_type): row = self.layout.row(align=True) - row.label( - text=work_time["Name"] or "Unnamed", icon="AUTO" if time_type == "WorkingTimes" else "HOME" - ) + row.label(text=work_time["Name"] or "Unnamed", icon="AUTO" if time_type == "WorkingTimes" else "HOME") if work_time["Start"] or work_time["Finish"]: row.label(text="{} - {}".format(work_time["Start"] or "*", work_time["Finish"] or "*")) if self.props.active_work_time_id == work_time["id"]: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py index 0d3bd2ded9..242d4f33b9 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py @@ -1,20 +1,23 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"rel_sequence": None, "lag_value": None, "duration_type": "NOTEDEFINED"} + self.settings = {"rel_sequence": None, "lag_value": None, "duration_type": "NOTDEFINED"} for key, value in settings.items(): self.settings[key] = value def execute(self): lag_value = self.file.createIfcDuration(self.settings["lag_value"]) - lag_time = self.file.create_entity("IfcLagTime", + lag_time = self.file.create_entity( + "IfcLagTime", **{ - "DurationType": self.settings["duration_type"], - "LagValue": lag_value, + "DurationType": self.settings["duration_type"], + "LagValue": lag_value, } ) - #Can an IfcLagTime entity be used by multiple sequence relationships? if self.settings["rel_sequence"].is_a("IfcRelSequence"): - if len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1: + if ( + self.settings["rel_sequence"].TimeLag + and len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1 + ): self.file.remove(self.settings["rel_sequence"].TimeLag) self.settings["rel_sequence"].TimeLag = lag_time diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 351b0aec83..a59b519e94 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -11,6 +11,7 @@ class Data: time_periods = {} tasks = {} task_times = {} + lag_times = {} sequences = {} @classmethod @@ -24,6 +25,7 @@ class Data: cls.time_periods = {} cls.tasks = {} cls.task_times = {} + cls.lag_times = {} cls.sequences = {} @classmethod @@ -39,6 +41,7 @@ class Data: cls.load_time_periods() cls.load_tasks() cls.load_task_times() + cls.load_lag_times() cls.load_sequences() cls.is_loaded = True @@ -158,6 +161,18 @@ class Data: data[key] = ifcopenshell.util.date.ifc2datetime(value) cls.task_times[task_time.id()] = data + @classmethod + def load_lag_times(cls): + cls.lag_times = {} + for lag_time in cls._file.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.lag_times[lag_time.id()] = data + @classmethod def load_sequences(cls): cls.sequences = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py index 67ef2523da..e415b93be7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py @@ -1,13 +1,18 @@ +import ifcopenshell.util.date + + class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"rel_sequence": None, "attributes": {}} + self.settings = {"lag_time": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value def execute(self): for name, value in self.settings["attributes"].items(): if name == "LagValue" and value is not None: - # TODO: support RatioMeasure - value = self.file.createIfcDuration(value) - setattr(self.settings["rel_sequence"], name, value) + if isinstance(value, float): + value = self.file.createIfcRatioMeasure(value) + else: + value = self.file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")) + setattr(self.settings["lag_time"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py index 872c64e25b..4654bac061 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py @@ -8,6 +8,6 @@ class Usecase: self.settings[key] = value def execute(self): - if self.settings["rel_sequence"].TimeLag.LagValue: - self.file.remove(self.settings["rel_sequence"].TimeLag.LagValue) - self.file.remove(self.settings["rel_sequence"].TimeLag) + if len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1: + return self.file.remove(self.settings["rel_sequence"].TimeLag) + self.settings["rel_sequence"].TimeLag = None