From 7188d6ea7485f268c9ea937b1fdb92e2dbbbf813 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Wed, 3 May 2023 16:09:27 +0100 Subject: [PATCH] BBIM 4D: implement human readable duration to read/edit durations. new IOS Fuzzy Duration Parsing utility --- .../blenderbim/bim/module/sequence/helper.py | 6 +- .../blenderbim/bim/module/sequence/prop.py | 12 +- .../blenderbim/bim/module/sequence/ui.py | 2 +- src/blenderbim/blenderbim/tool/sequence.py | 4 +- .../ifcopenshell/util/date.py | 115 +++++++++++++++++- 5 files changed, 116 insertions(+), 23 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index cdb078f096..99d61c7611 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -33,11 +33,7 @@ def parse_datetime(value): def parse_duration(value): - try: - return isodate.parse_duration(value) - except: - return None - + return ifcopenshell.util.date.parse_duration(value) def canonicalise_time(time): if not time: diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 2e146a596d..cf81f8da40 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -203,9 +203,8 @@ def updateTaskDuration(self, context): if self.duration == "-": return - try: - isodate.parse_duration(self.duration), - except: + duration = ifcopenshell.util.date.parse_duration(self.duration) + if not duration: self.duration = "-" return @@ -215,17 +214,12 @@ def updateTaskDuration(self, context): task_time = task.TaskTime else: task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task) - SequenceData.load() ifcopenshell.api.run( "sequence.edit_task_time", self.file, - **{"task_time": task_time, "attributes": {"ScheduleDuration": self.duration}}, + **{"task_time": task_time, "attributes": {"ScheduleDuration": duration}}, ) SequenceData.load() - if props.active_task_id == self.ifc_definition_id: - attribute = props.task_time_attributes.get("Duration") - if attribute: - attribute.string_value = self.duration bpy.ops.bim.load_task_properties() diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 63277d5f5d..97a53930c9 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -374,7 +374,7 @@ class BIM_PT_work_schedules(Panel): def draw_editable_task_ui(self, work_schedule_id): row = self.layout.row(align=True) - row.label(text="") + row.label(text="Task Tools") row = self.layout.row(align=True) row.alignment = "RIGHT" row.operator("bim.add_summary_task", text="Add Summary Task", icon="ADD").work_schedule = work_schedule_id diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index ea165140ec..46b7876a55 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -225,7 +225,7 @@ class Sequence(blenderbim.core.tool.Sequence): else "-" ) item.duration = ( - isodate.duration_isoformat(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration)) + str(ifcopenshell.util.date.readable_ifc_duration(task_time.ScheduleDuration)) if task_time.ScheduleDuration else "-" ) @@ -243,7 +243,7 @@ class Sequence(blenderbim.core.tool.Sequence): item.finish = "-" item.duration = "-" - bpy.context.scene.BIMWorkScheduleProperties.is_task_update_enabled = True + props.is_task_update_enabled = True @classmethod def get_active_work_schedule(cls): diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index 6c4b645477..d99e23f632 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -35,14 +35,15 @@ def timedelta2duration(timedelta): } if components["seconds"]: components["hours"], components["minutes"], components["seconds"] = [ - int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":") + int(i) + for i in str(datetime.timedelta(seconds=components["seconds"])).split(":") ] return isodate.Duration(**components) def ifc2datetime(element): if isinstance(element, str) and "P" in element[0:2]: # IfcDuration - duration = isodate.parse_duration(element) + duration = parse_duration(element) if isinstance(duration, datetime.timedelta): return timedelta2duration(duration) return duration @@ -72,6 +73,38 @@ def ifc2datetime(element): ) +def get_isosplit(s, split): + if split in s: + n, s = s.split(split) + else: + n = 0 + return n, s + + +def readable_ifc_duration(string): + string = string.split("P")[-1] + + years, string = get_isosplit(string, "Y") + months, string = get_isosplit(string, "M") + weeks, string = get_isosplit(string, "W") + days, string = get_isosplit(string, "D") + _, string = get_isosplit(string, "T") + hours, string = get_isosplit(string, "H") + minutes, string = get_isosplit(string, "M") + seconds, string = get_isosplit(string, "S") + + final_string = "" + final_string += f"{years} y " if years else "" + final_string += f"{months} m " if months else "" + final_string += f"{weeks} w " if weeks else "" + final_string += f"{days} d " if days else "" + final_string += f"{hours} h " if hours else "" + final_string += f"{minutes} m " if minutes else "" + final_string += f"{seconds} s " if seconds else "" + + return final_string + + def datetime2ifc(dt, ifc_type): if isinstance(dt, str): if ifc_type == "IfcDuration": @@ -89,7 +122,9 @@ def datetime2ifc(dt, ifc_type): if isinstance(dt, datetime.datetime): return dt.isoformat() elif isinstance(dt, datetime.date): - return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat() + return datetime.datetime.combine( + dt, datetime.datetime.min.time() + ).isoformat() elif ifc_type == "IfcDate": if isinstance(dt, datetime.datetime): return dt.date().isoformat() @@ -101,10 +136,19 @@ def datetime2ifc(dt, ifc_type): elif isinstance(dt, datetime.time): return dt.isoformat() elif ifc_type == "IfcCalendarDate": - return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year} + return { + "DayComponent": dt.day, + "MonthComponent": dt.month, + "YearComponent": dt.year, + } elif ifc_type == "IfcLocalTime": # TODO implement timezones - return {"HourComponent": dt.hour, "MinuteComponent": dt.minute, "SecondComponent": dt.second} + return { + "HourComponent": dt.hour, + "MinuteComponent": dt.minute, + "SecondComponent": dt.second, + } + def string_to_date(string): if not string: @@ -117,6 +161,7 @@ def string_to_date(string): except: return None + def string_to_duration(duration_string): # TODO support years, months, weeks aswell days = 0 @@ -135,4 +180,62 @@ def string_to_duration(duration_string): match = findall(r"(\d+\.?\d*)s", duration_string) if match: seconds = float(match[0]) - return isodate.duration_isoformat(datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)) \ No newline at end of file + return isodate.duration_isoformat( + datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds) + ) + + +def parse_duration(value): + print("parsing duration", value) + if not value: + return None + if isinstance(value, str): + if "P" in value: + try: + return isodate.parse_duration(value) + except: + print("error parsing ISO string duration") + return None + else: + try: + final_string = "P" + value_upper = value.upper() + for char in value_upper: + if char.isdigit(): + final_string += char + elif char == "D": + final_string += "D" + if ( + "H" in value_upper + or "S" in value_upper + or "MIN" in value_upper + ): + final_string += "T" + elif char == "W": + final_string += "W" + elif char == "M": + final_string += "M" + elif char == "Y": + final_string += "Y" + elif char == "H": + final_string = ( + final_string[:1] + "T" + final_string[1:] + if "T" not in final_string + else final_string + ) + final_string += "H" + elif char == "M": + if "MIN" in value_upper and "T" not in final_string: + final_string = final_string[:1] + "T" + final_string[1:] + final_string += "M" + elif char == "S": + final_string = ( + final_string[:1] + "T" + final_string[1:] + if "T" not in final_string + else final_string + ) + final_string += "S" + return isodate.parse_duration(final_string) + except: + print("error fuzzy parsing duration") + return None