From 8a3e02340870746190858f287b2305f4a3705efe Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 6 May 2021 17:14:28 +1000 Subject: [PATCH] Fix unfinished time/date/duration handling in edit task time, work plan, work schedule, and finally refactor attribute i/o like I promised --- src/blenderbim/blenderbim/bim/helper.py | 199 +++------- .../blenderbim/bim/module/sequence/helper.py | 18 + .../bim/module/sequence/operator.py | 340 +++++------------- .../api/sequence/edit_task_time.py | 7 +- .../api/sequence/edit_work_plan.py | 8 + .../api/sequence/edit_work_schedule.py | 8 + 6 files changed, 177 insertions(+), 403 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 218eb4294d..c51175d88b 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -1,153 +1,62 @@ +import bpy +import json import math +import ifcopenshell +import ifcopenshell.util.attribute from mathutils import geometry from mathutils import Vector -import bpy +from blenderbim.bim.ifc import IfcStore -# TODO: Deprecate this in favour of ifcopenshell.util.unit +def import_attributes(ifc_class, props, data, callback=None): + for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity": + continue + new = props.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + new.is_optional = attribute.optional() + new.data_type = data_type + is_handled_by_callback = callback(attribute.name(), new, data) if callback else False + if is_handled_by_callback: + pass # Our job is done + elif data_type == "string": + new.string_value = "" if new.is_null else data[attribute.name()] + elif data_type == "boolean": + new.bool_value = False if new.is_null else data[attribute.name()] + elif data_type == "integer": + new.int_value = 0 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 SIUnitHelper: - prefixes = { - "EXA": 1e18, - "PETA": 1e15, - "TERA": 1e12, - "GIGA": 1e9, - "MEGA": 1e6, - "KILO": 1e3, - "HECTO": 1e2, - "DECA": 1e1, - "DECI": 1e-1, - "CENTI": 1e-2, - "MILLI": 1e-3, - "MICRO": 1e-6, - "NANO": 1e-9, - "PICO": 1e-12, - "FEMTO": 1e-15, - "ATTO": 1e-18, - } - unit_names = [ - "AMPERE", - "BECQUEREL", - "CANDELA", - "COULOMB", - "CUBIC_METRE", - "DEGREE CELSIUS", - "FARAD", - "GRAM", - "GRAY", - "HENRY", - "HERTZ", - "JOULE", - "KELVIN", - "LUMEN", - "LUX", - "MOLE", - "NEWTON", - "OHM", - "PASCAL", - "RADIAN", - "SECOND", - "SIEMENS", - "SIEVERT", - "SQUARE METRE", - "METRE", - "STERADIAN", - "TESLA", - "VOLT", - "WATT", - "WEBER", - ] - si_conversions = { - "inch": 0.0254, - "foot": 0.3048, - "yard": 0.914, - "mile": 1609, - "square inch": 0.0006452, - "square foot": 0.09290304, - "square yard": 0.83612736, - "acre": 4046.86, - "square mile": 2588881, - "cubic inch": 0.00001639, - "cubic foot": 0.02831684671168849, - "cubic yard": 0.7636, - "litre": 0.001, - "fluid ounce UK": 0.0000284130625, - "fluid ounce US": 0.00002957353, - "pint UK": 0.000568, - "pint US": 0.000473, - "gallon UK": 0.004546, - "gallon US": 0.003785, - "degree": math.pi / 180, - "ounce": 0.02835, - "pound": 0.454, - "ton UK": 1016.0469088, - "ton US": 907.18474, - "lbf": 4.4482216153, - "kip": 4448.2216153, - "psi": 6894.7572932, - "ksi": 6894757.2932, - "minute": 60, - "hour": 3600, - "day": 86400, - "btu": 1055.056, - } +def export_attributes(props, callback=None): + attributes = {} + for attribute in props: + is_handled_by_callback = callback(attributes, attribute) if callback else False + if attribute.is_null: + attributes[attribute.name] = None + elif is_handled_by_callback: + pass # Our job is done + elif attribute.data_type == "string": + attributes[attribute.name] = attribute.string_value + elif attribute.data_type == "boolean": + attributes[attribute.name] = attribute.bool_value + elif attribute.data_type == "integer": + attributes[attribute.name] = attribute.int_value + elif attribute.data_type == "float": + attributes[attribute.name] = attribute.float_value + elif attribute.data_type == "enum": + attributes[attribute.name] = attribute.enum_value + return attributes - @staticmethod - def get_prefix(text): - for prefix in SIUnitHelper.prefixes.keys(): - if prefix in text.upper(): - return prefix - @staticmethod - def get_prefix_multiplier(text): - if not text: - return 1 - prefix = SIUnitHelper.get_prefix(text) - if prefix: - return SIUnitHelper.prefixes[prefix] - return 1 - - @staticmethod - def get_unit_name(text): - for name in SIUnitHelper.unit_names: - if name in text.upper().replace("METER", "METRE"): - return name - - @staticmethod - def convert(value, from_prefix, from_unit, to_prefix, to_unit): - """Converts between length, area, and volume units - - :param value: The numeric value you want to convert - :type value: float - :param from_prefix: A prefix from IfcSIPrefix. Can be None. - :type from_prefix: string - :param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type from_unit: string - :param to_prefix: A prefix from IfcSIPrefix. Can be None. - :type to_prefix: string - :param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type to_unit: string - """ - if from_unit in SIUnitHelper.si_conversions: - value *= SIUnitHelper.si_conversions[from_unit] - elif from_prefix: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - if "SQUARE" in from_unit: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - elif "CUBIC" in from_unit: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - if to_unit in SIUnitHelper.si_conversions: - return value * (1 / SIUnitHelper.si_conversions[to_unit]) - elif to_prefix: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - if "SQUARE" in from_unit: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - elif "CUBIC" in from_unit: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - return value +# TODO: migrate the below helper functions into the drawing module, since it is specific to that module # This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py @@ -302,7 +211,7 @@ def parse_diagram_scale(camera): def get_project_collection(scene): """Get main project collection""" - colls = [c for c in scene.collection.children if c.name.startswith('IfcProject')] + colls = [c for c in scene.collection.children if c.name.startswith("IfcProject")] if len(colls) != 1: raise RuntimeError("project collection missing or not unique") return colls[0] @@ -315,7 +224,7 @@ def get_active_drawing(scene): return None, None try: drawing = props.drawings[props.active_drawing_index] - return scene.collection.children['Views'].children[f"IfcGroup/{drawing.name}"], drawing.camera + return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera except (KeyError, IndexError): raise RuntimeError("missing drawing collection") @@ -333,8 +242,8 @@ def ortho_view_frame(camera, margin=0.015): """ aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x size = camera.ortho_scale - hwidth = size * .5 - hheight = size * .5 * aspect + hwidth = size * 0.5 + hheight = size * 0.5 * aspect scale = parse_diagram_scale(camera) xmarg = margin * scale ymarg = margin * scale * aspect diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index f7144628cd..8046e09174 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -1,3 +1,5 @@ +import isodate +from dateutil import parser from ifcopenshell.api.sequence.data import Data @@ -18,3 +20,19 @@ def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, if current_date and (date is None or current_date > date): date = current_date return date + + +def parse_datetime(value): + try: + return parser.isoparse(value) + except: + try: + return parser.parse(value, dayfirst=True, fuzzy=True) + except: + return None + +def parse_duration(value): + try: + return isodate.parse_duration(value) + except: + return None diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index dfa99cd789..34fcece570 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -8,6 +8,7 @@ import pystache import webbrowser import ifcopenshell.api import ifcopenshell.util.date +import blenderbim.bim.helper import blenderbim.bim.module.sequence.helper as helper from datetime import datetime from datetime import timedelta @@ -33,15 +34,7 @@ class EditWorkPlan(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkPlanProperties - attributes = {} - for attribute in props.work_plan_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.work_plan_attributes, self.export_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_plan", @@ -52,6 +45,14 @@ class EditWorkPlan(bpy.types.Operator): bpy.ops.bim.disable_editing_work_plan() return {"FINISHED"} + def export_attributes(self, attributes, prop): + if "Date" in prop.name or "Time" in prop.name: + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "Duration" or prop.name == "TotalFloat": + attributes[prop.name] = helper.parse_duration(prop.string_value) + return True + class RemoveWorkPlan(bpy.types.Operator): bl_idname = "bim.remove_work_plan" @@ -77,27 +78,17 @@ class EnableEditingWorkPlan(bpy.types.Operator): data = Data.work_plans[self.work_plan] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkPlan").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.work_plan_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if attribute.name() in ["CreationDate", "StartTime", "FinishTime"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcWorkPlan", props.work_plan_attributes, data, self.import_attributes) + props.active_work_plan_id = self.work_plan props.editing_type = "ATTRIBUTES" return {"FINISHED"} + 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 DisableEditingWorkPlan(bpy.types.Operator): bl_idname = "bim.disable_editing_work_plan" @@ -176,15 +167,7 @@ class EditWorkSchedule(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.work_schedule_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + 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", @@ -195,6 +178,14 @@ class EditWorkSchedule(bpy.types.Operator): 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: + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "Duration" or prop.name == "TotalFloat": + attributes[prop.name] = helper.parse_duration(prop.string_value) + return True + class RemoveWorkSchedule(bpy.types.Operator): bl_idname = "bim.remove_work_schedule" @@ -227,23 +218,14 @@ class EnableEditingWorkSchedule(bpy.types.Operator): def enable_editing_work_schedule(self): data = Data.work_schedules[self.work_schedule] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_schedule_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() in ["CreationDate", "StartTime", "FinishTime"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + 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): @@ -439,39 +421,21 @@ class EnableEditingTaskTime(bpy.types.Operator): data = Data.task_times[task_time_id] - for attribute in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.task_time_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - 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") - ) - else: - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False 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()] + 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(IfcStore.get_file()) @@ -484,20 +448,7 @@ class EditTaskTime(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.task_time_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 == "boolean": - attributes[attribute.name] = attribute.bool_value - elif attribute.data_type == "float": - attributes[attribute.name] = attribute.float_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value - + attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_attributes) attributes = self.convert_strings_to_date_times(attributes) self.file = IfcStore.get_file() @@ -511,21 +462,13 @@ class EditTaskTime(bpy.types.Operator): bpy.ops.bim.load_task_properties(task=props.active_task_id) return {"FINISHED"} - def convert_strings_to_date_times(self, attributes): - for key, value in attributes.items(): - if not value: - continue - if "Start" in key or "Finish" in key or key == "StatusTime": - try: - attributes[key] = parser.isoparse(value) - except: - try: - attributes[key] = parser.parse(value, dayfirst=True, fuzzy=True) - except: - attributes[key] = None - elif key == "ScheduleDuration": - attributes[key] = isodate.parse_duration(value) - return attributes + def export_attributes(self, attributes, prop): + if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": + attributes[prop.name] = helper.parse_datetime(value) + return True + elif prop.name == "ScheduleDuration": + attributes[prop.name] = helper.parse_duration(value) + return True class EnableEditingTask(bpy.types.Operator): @@ -540,25 +483,7 @@ class EnableEditingTask(bpy.types.Operator): data = Data.tasks[self.task] - for attribute in IfcStore.get_schema().declaration_by_name("IfcTask").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.task_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else data[attribute.name()] - elif data_type == "integer": - new.int_value = 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()] + blenderbim.bim.helper.import_attributes("IfcTask", props.task_attributes, data) props.active_task_id = self.task props.editing_task_type = "ATTRIBUTES" return {"FINISHED"} @@ -580,19 +505,7 @@ class EditTask(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.task_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 == "boolean": - attributes[attribute.name] = attribute.bool_value - elif attribute.data_type == "integer": - attributes[attribute.name] = attribute.int_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.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} @@ -778,15 +691,7 @@ class EditWorkCalendar(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkCalendarProperties - attributes = {} - for attribute in props.work_calendar_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.work_calendar_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_calendar", @@ -824,21 +729,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator): data = Data.work_calendars[self.work_calendar] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkCalendar").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_calendar_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + 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" return {"FINISHED"} @@ -936,29 +827,20 @@ class EnableEditingWorkTime(bpy.types.Operator): data = Data.work_times[self.work_time] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkTime").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_time_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() in ["Start", "Finish"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + 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 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): @@ -1014,15 +896,7 @@ class EditWorkTime(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkCalendarProperties - attributes = {} - for attribute in self.props.work_time_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(self.props.work_time_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_time", @@ -1251,21 +1125,7 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): def enable_editing_sequence_attributes(self): data = Data.sequences[self.sequence] - for attribute in IfcStore.get_schema().declaration_by_name("IfcRelSequence").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.sequence_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcRelSequence", self.props.sequence_attributes, data) class EnableEditingSequenceTimeLag(bpy.types.Operator): @@ -1285,34 +1145,24 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator): 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()] + 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": + 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): @@ -1351,15 +1201,7 @@ class EditSequenceAttributes(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.sequence_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.sequence_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_sequence", @@ -1378,17 +1220,7 @@ class EditSequenceTimeLag(bpy.types.Operator): 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 + attributes = blenderbim.bim.helper.export_attributes(props.time_lag_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_lag_time", diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 0680a5ece2..1bede081db 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -10,10 +10,9 @@ class Usecase: def execute(self): for name, value in self.settings["attributes"].items(): - if "Start" in name or "Finish" in name or name == "StatusTime": - if value: + if value: + if "Start" in name or "Finish" in name or name == "StatusTime": value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") - if name == "ScheduleDuration": - if value: + elif name == "ScheduleDuration": value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") setattr(self.settings["task_time"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py index 3830f0c2c1..48b8b0dcde 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py @@ -1,3 +1,6 @@ +import ifcopenshell.util.date + + class Usecase: def __init__(self, file, **settings): self.file = file @@ -7,4 +10,9 @@ class Usecase: def execute(self): for name, value in self.settings["attributes"].items(): + if value: + if "Date" in name or "Time" in name: + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") + elif name == "Duration" or name == "TotalFloat": + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") setattr(self.settings["work_plan"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py index e6782c30d1..a2d57910da 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py @@ -1,3 +1,6 @@ +import ifcopenshell.util.date + + class Usecase: def __init__(self, file, **settings): self.file = file @@ -7,4 +10,9 @@ class Usecase: def execute(self): for name, value in self.settings["attributes"].items(): + if value: + if "Date" in name or "Time" in name: + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") + elif name == "Duration" or name == "TotalFloat": + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") setattr(self.settings["work_schedule"], name, value)