From cb724dbfde61cb8ef0eb9182b8510ae8536a2e70 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 24 Jul 2025 17:57:38 +0500 Subject: [PATCH] Work schedule UI - fix issue with some durations props not loading E.g. IfcWorkSchedule.Duration wasn't recognized as such or IfcWorkSchedule.TotalFloat wasn't imported properly and was uneditable. Now we also check IfcDuration type on attributes explicitly, instead of relying on hardcoded list --- src/bonsai/bonsai/bim/helper.py | 2 +- .../bonsai/bim/module/sequence/helper.py | 6 +- src/bonsai/bonsai/bim/prop.py | 13 ++- src/bonsai/bonsai/tool/sequence.py | 90 +++++++++++-------- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index 8f7134b061..e6c165cd22 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -111,7 +111,7 @@ def draw_attribute( op = layout.operator("bim.attribute_add_subitem", icon="ADD", text="") op.data_path = data_path - elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"): + elif attribute.special_type == "DURATION": props = tool.Sequence.get_work_schedule_props() for item in props.durations_attributes: if item.name == attribute.name: diff --git a/src/bonsai/bonsai/bim/module/sequence/helper.py b/src/bonsai/bonsai/bim/module/sequence/helper.py index 11cf7336b2..43dcd1d59b 100644 --- a/src/bonsai/bonsai/bim/module/sequence/helper.py +++ b/src/bonsai/bonsai/bim/module/sequence/helper.py @@ -23,7 +23,7 @@ import bpy from dateutil import parser import ifcopenshell.util.date from datetime import timedelta, datetime -from typing import Union +from typing import Union, Any from bonsai.bim.prop import ISODuration @@ -49,8 +49,8 @@ def canonicalise_time(time: Union[datetime, None]) -> str: return time.strftime("%d/%m/%y") -def parse_duration_as_blender_props(dt, simplify=True): - if simplify: +def parse_duration_as_blender_props(dt: Union[Any, str]) -> dict[str, int]: + if True: if isinstance(dt, str): dt = ifcopenshell.util.date.ifc2datetime(dt) diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index 70adc430b7..7a4464c986 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -274,7 +274,18 @@ def get_display_name(self: "Attribute") -> str: AttributeDataType = Literal["string", "integer", "float", "boolean", "enum", "file", "list[string]"] -AttributeSpecialType = Literal["", "DATE", "DATETIME", "LENGTH", "AREA", "VOLUME", "FORCE", "LOGICAL", "URI"] +AttributeSpecialType = Literal[ + "", + "DATE", + "DATETIME", + "LENGTH", + "AREA", + "VOLUME", + "FORCE", + "LOGICAL", + "URI", + "DURATION", +] class Attribute(PropertyGroup): diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 7497bba0e4..b24341c5d9 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -146,12 +146,8 @@ class Sequence(bonsai.core.tool.Sequence): 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 + elif prop.special_type == "DURATION": + return cls.export_duration_prop(prop, attributes) return False props = cls.get_work_schedule_props() @@ -159,16 +155,58 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_work_schedule_attributes(cls, work_schedule: ifcopenshell.entity_instance) -> None: + schema = tool.Ifc.schema() + entity = schema.declaration_by_name("IfcWorkSchedule").as_entity() + assert entity + def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]) -> None | Literal[True]: if name in ["CreationDate", "StartTime", "FinishTime"]: assert prop prop.string_value = "" if prop.is_null else data[name] return True + else: + attr = entity.attribute_by_index(entity.attribute_index(name)) + if not attr.type_of_attribute()._is("IfcDuration"): + return + assert prop + cls.add_duration_prop(prop, data[name]) props = cls.get_work_schedule_props() props.work_schedule_attributes.clear() bonsai.bim.helper.import_attributes(work_schedule, props.work_schedule_attributes, callback) + @classmethod + def add_duration_prop(cls, prop: Attribute, duration_value: Union[str, None]) -> None: + import bonsai.bim.module.sequence.helper as helper + + props = cls.get_work_schedule_props() + prop.special_type = "DURATION" + duration_props = props.durations_attributes.add() + duration_props.name = prop.name + if duration_value is None: + return + for key, value in helper.parse_duration_as_blender_props(duration_value).items(): + setattr(duration_props, key, value) + + @classmethod + def export_duration_prop(cls, prop: Attribute, out_attributes: dict[str, Any]) -> Literal[True]: + import bonsai.bim.module.sequence.helper as helper + + props = cls.get_work_schedule_props() + if prop.is_null: + out_attributes[prop.name] = None + for value in props.durations_attributes.values(): + value = 0 + else: + duration_type = out_attributes["DurationType"] if "DurationType" in out_attributes else None + time_split_iso_duration = helper.blender_props_to_iso_duration( + props.durations_attributes, duration_type, prop.name + ) + out_attributes[prop.name] = time_split_iso_duration + for value in props.durations_attributes.values(): + value = 0 + return True + @classmethod def enable_editing_work_schedule(cls, work_schedule: ifcopenshell.entity_instance) -> None: props = cls.get_work_schedule_props() @@ -401,25 +439,16 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def load_task_time_attributes(cls, task_time: ifcopenshell.entity_instance) -> None: - import bonsai.bim.module.sequence.helper as helper - props = cls.get_work_schedule_props() + schema = tool.Ifc.schema() + entity = schema.declaration_by_name("IfcTaskTime").as_entity() + assert entity def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]) -> Union[bool, None]: - if prop and prop.data_type == "string": - # TODO: Check actual attribute type instead of providing attribute names. - if name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"): - duration_props = props.durations_attributes.add() - duration_props.name = name - if prop.is_null: - for key in duration_props.keys(): - if key != "name": - setattr(duration_props, key, 0) - return True - if data[name]: - for key, value in helper.parse_duration_as_blender_props(data[name]).items(): - duration_props[key] = value - return True + attr = entity.attribute_by_index(entity.attribute_index(name)) + if attr.type_of_attribute()._is("IfcDuration"): + assert prop + cls.add_duration_prop(prop, data[name]) if isinstance(data[name], datetime): assert prop prop.string_value = "" if prop.is_null else data[name].isoformat() @@ -456,21 +485,8 @@ class Sequence(bonsai.core.tool.Sequence): return True attributes[prop.name] = helper.parse_datetime(prop.string_value) return True - elif prop.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]: - if prop.is_null: - attributes[prop.name] = None - for value in props.durations_attributes.values(): - value = 0 - return True - else: - duration_type = attributes["DurationType"] if "DurationType" in attributes else None - time_split_iso_duration = helper.blender_props_to_iso_duration( - props.durations_attributes, duration_type, prop.name - ) - attributes[prop.name] = time_split_iso_duration - for value in props.durations_attributes.values(): - value = 0 - return True + elif prop.special_type == "DURATION": + return cls.export_duration_prop(prop, attributes) return False return bonsai.bim.helper.export_attributes(props.task_time_attributes, callback)