From 5c9984a12a8cddac9a943b8a58422ae38b68c8ab Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 21 Feb 2022 13:51:26 +1100 Subject: [PATCH] Fix bug with broken MS Project to IFC conversion --- src/ifc4d/ifc4d/common.py | 60 ++++++------- src/ifc4d/ifc4d/msp2ifc.py | 167 ++++++++++++++++++++++++++++++------- 2 files changed, 163 insertions(+), 64 deletions(-) diff --git a/src/ifc4d/ifc4d/common.py b/src/ifc4d/ifc4d/common.py index d341f113b9..e12281dd5b 100644 --- a/src/ifc4d/ifc4d/common.py +++ b/src/ifc4d/ifc4d/common.py @@ -6,17 +6,16 @@ from datetime import datetime, timedelta, date class ScheduleIfcGenerator: - def __init__(self, file, output, settings): self.file = file - self.work_plan = settings['work_plan'] - self.project = settings['project'] - self.calendars = settings['calendars'] - self.wbs = settings['wbs'] - self.root_activites = settings['root_activities'] - self.activities = settings['activities'] - self.relationships = settings['relationships'] - self.resources = settings['resources'] + self.work_plan = settings["work_plan"] + self.project = settings["project"] + self.calendars = settings["calendars"] + self.wbs = settings["wbs"] + self.root_activites = settings["root_activities"] + self.activities = settings["activities"] + self.relationships = settings["relationships"] + self.resources = settings["resources"] self.output = output self.day_map = { "Monday": 1, @@ -27,7 +26,7 @@ class ScheduleIfcGenerator: "Saturday": 6, "Sunday": 7, } - + def create_ifc(self): if not self.file: self.file = self.create_boilerplate_ifc() @@ -48,9 +47,7 @@ class ScheduleIfcGenerator: def create_calendars(self): for calendar in self.calendars.values(): - calendar["ifc"] = ifcopenshell.api.run( - "sequence.add_work_calendar", self.file, name=calendar["Name"] - ) + calendar["ifc"] = ifcopenshell.api.run("sequence.add_work_calendar", self.file, name=calendar["Name"]) self.process_working_week(calendar["StandardWorkWeek"], calendar["ifc"]) self.process_exceptions(calendar.get("HolidayOrExceptions"), calendar["ifc"]) @@ -226,12 +223,12 @@ class ScheduleIfcGenerator: "Identification": str(activity["Identification"]), "Status": activity["Status"], "IsMilestone": activity["StartDate"] == activity["FinishDate"], - "PredefinedType": "CONSTRUCTION" + "PredefinedType": "CONSTRUCTION", }, ) task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=activity["ifc"]) calendar = self.calendars[activity["CalendarObjectId"]] - #print(calendar, self.calendars) + # print(calendar, self.calendars) # Seems intermittently crashy - can we investigate for larger files? ifcopenshell.api.run( "control.assign_control", @@ -288,43 +285,36 @@ class ScheduleIfcGenerator: lag_value=timedelta(days=lag / float(calendar["HoursPerDay"] or 8)), duration_type="WORKTIME", ) - - + def create_resources(self): # print("Resources", self.resources) if self.resources: for id, resource in self.resources.items(): - + parent = self.resources.get(resource.get("ParentObjectId")) if parent: if not parent.get("ifc"): parent["ifc"] = ifcopenshell.api.run( "resource.add_resource", self.file, - **{"ifc_class": "IfcCrewResource", - "name": parent['Name']} + **{"ifc_class": "IfcCrewResource", "name": parent["Name"]}, ) if parent: resource["ifc"] = ifcopenshell.api.run( - "resource.add_resource", - self.file, - **{"parent_resource": parent["ifc"] if parent else None, - "ifc_class": "IfcCrewResource", - "name": resource['Name'] - } - ) + "resource.add_resource", + self.file, + **{ + "parent_resource": parent["ifc"] if parent else None, + "ifc_class": "IfcCrewResource", + "name": resource["Name"], + }, + ) else: resource["ifc"] = ifcopenshell.api.run( - "resource.add_resource", - self.file, - **{ "ifc_class": "IfcCrewResource", - "name": resource['Name'] - } - ) + "resource.add_resource", self.file, **{"ifc_class": "IfcCrewResource", "name": resource["Name"]} + ) print(self.resources) - - def create_boilerplate_ifc(self): self.file = ifcopenshell.file(schema="IFC4") self.work_plan = self.file.create_entity("IfcWorkPlan") diff --git a/src/ifc4d/ifc4d/msp2ifc.py b/src/ifc4d/ifc4d/msp2ifc.py index 100fd03378..b8a2dfe157 100644 --- a/src/ifc4d/ifc4d/msp2ifc.py +++ b/src/ifc4d/ifc4d/msp2ifc.py @@ -1,4 +1,3 @@ - # Ifc4D - IFC scheduling utility # Copyright (C) 2021 Dion Moult # @@ -23,7 +22,6 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.util.date import xml.etree.ElementTree as ET -from .common import ScheduleIfcGenerator class MSP2Ifc: @@ -34,34 +32,13 @@ class MSP2Ifc: self.work_plan = None self.project = {} self.calendars = {} - self.wbs = {} - self.root_activites = [] - self.activities = [] self.tasks = {} - self.relationships = {} self.resources = {} - self.output = None - self.RESOURCE_TYPES_MAPPING = { - '1' : "LABOR", - '0' : "MATERIAL", - '2' : None - } - + self.RESOURCE_TYPES_MAPPING = {"1": "LABOR", "0": "MATERIAL", "2": None} def execute(self): self.parse_xml() - settings = { - "work_plan":self.work_plan, - "project": self.project, - "calendars": self.calendars, - "wbs": self.wbs, - "root_activities": self.root_activites, - "activities": self.activities, - "relationships": self.relationships, - "resources": self.resources - } - ifcCreator = ScheduleIfcGenerator(self.file, self.output, settings) - ifcCreator.create_ifc() + self.create_ifc() def parse_xml(self): tree = ET.parse(self.xml) @@ -72,8 +49,8 @@ class MSP2Ifc: self.outline_parents = {} self.parse_task_xml(project) self.parse_calendar_xml(project) - self.parse_resources_xml(project) - + # TODO Doesn't do anything right now + # self.parse_resources_xml(project) def parse_relationship_xml(self, task): relationships = {} @@ -143,7 +120,139 @@ class MSP2Ifc: "Name": calendar.find("pr:Name", self.ns).text, "StandardWorkWeek": week_days, } - + + def create_ifc(self): + if not self.file: + self.create_boilerplate_ifc() + if not self.work_plan: + self.work_plan = ifcopenshell.api.run("sequence.add_work_plan", self.file) + work_schedule = self.create_work_schedule() + self.create_tasks(work_schedule) + self.create_calendars() + self.create_rel_sequences() + + def create_boilerplate_ifc(self): + self.file = ifcopenshell.file(schema="IFC4") + self.work_plan = self.file.create_entity("IfcWorkPlan") + + def create_tasks(self, work_schedule): + for task_id in self.tasks: + task = self.tasks[task_id] + if task["OutlineLevel"] == 0: + self.create_task(task, work_schedule=work_schedule) + + def create_work_schedule(self): + return ifcopenshell.api.run( + "sequence.add_work_schedule", self.file, name=self.project["Name"], work_plan=self.work_plan + ) + + def create_calendars(self): + for calendar in self.calendars.values(): + calendar["ifc"] = ifcopenshell.api.run("sequence.add_work_calendar", self.file, name=calendar["Name"]) + self.process_working_week(calendar["StandardWorkWeek"], calendar["ifc"]) + + def create_task(self, task, work_schedule=None, parent_task=None): + task["ifc"] = ifcopenshell.api.run( + "sequence.add_task", + self.file, + work_schedule=work_schedule if work_schedule else None, + parent_task=parent_task["ifc"] if parent_task else None, + ) + ifcopenshell.api.run( + "sequence.edit_task", + self.file, + task=task["ifc"], + attributes={ + "Name": task["Name"], + "Identification": task["OutlineNumber"], + "IsMilestone": task["Start"] == task["Finish"], + }, + ) + task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task["ifc"]) + ifcopenshell.api.run( + "sequence.edit_task_time", + self.file, + task_time=task_time, + attributes={ + "ScheduleStart": task["Start"], + "ScheduleFinish": task["Finish"], + "DurationType": "WORKTIME" if task["Duration"] else None, + "ScheduleDuration": task["Duration"] if task["Duration"] else None, + }, + ) + for subtask_id in task["subtasks"]: + self.create_task(self.tasks[subtask_id], parent_task=task) + + def process_working_week(self, week, calendar): + for day in week: + if day["ifc"]: + continue + + day["ifc"] = ifcopenshell.api.run( + "sequence.add_work_time", self.file, work_calendar=calendar, time_type="WorkingTimes" + ) + + weekday_component = [int(day["DayType"])] + for day2 in week: + if day["DayType"] == day2["DayType"]: + continue + if day["WorkingTimes"] == day2["WorkingTimes"]: + weekday_component.append(int(day2["DayType"])) + # Don't process the next day, as we can group it + day2["ifc"] = day["ifc"] + + work_time_name = "Weekdays: {}".format(", ".join([str(c) for c in sorted(weekday_component)])) + ifcopenshell.api.run( + "sequence.edit_work_time", + self.file, + work_time=day["ifc"], + attributes={"Name": work_time_name}, + ) + + recurrence = ifcopenshell.api.run( + "sequence.assign_recurrence_pattern", self.file, parent=day["ifc"], recurrence_type="WEEKLY" + ) + ifcopenshell.api.run( + "sequence.edit_recurrence_pattern", + self.file, + recurrence_pattern=recurrence, + attributes={"WeekdayComponent": weekday_component}, + ) + for work_time in day["WorkingTimes"]: + ifcopenshell.api.run( + "sequence.add_time_period", + self.file, + recurrence_pattern=recurrence, + start_time=work_time["Start"], + end_time=work_time["Finish"], + ) + + def create_rel_sequences(self): + self.sequence_type_map = { + "1": "START_START", + "2": "START_FINISH", + "3": "FINISH_START", + "4": "FINISH_FINISH", + "0": "NOTDEFINED", + } + for task in self.tasks.values(): + if not task["PredecessorTasks"]: + continue + for predecessor in task["PredecessorTasks"].values(): + rel_sequence = ifcopenshell.api.run( + "sequence.assign_sequence", + self.file, + related_process=task["ifc"], + relating_process=self.tasks[predecessor["PredecessorTask"]]["ifc"], + ) + if predecessor["Type"]: + ifcopenshell.api.run( + "sequence.edit_sequence", + self.file, + rel_sequence=rel_sequence, + attributes={"SequenceType": self.sequence_type_map[predecessor["Type"]]}, + ) + def parse_resources_xml(self, project): resources_lst = project.find("pr:Resources", self.ns) resources = resources_lst.findall("pr:Resource", self.ns) @@ -152,7 +261,7 @@ class MSP2Ifc: name = resource.find("pr:Name", self.ns) id = resource.find("pr:ID", self.ns).text if name is not None: - name = name.text + name = name.text else: # print("- No Name") name = None