Added support for Powerproject (#1849)

* implemented initial cli and support for resource parsing for ms project

* Added command line interface and resource support for MS Project XML files

* Removed the test code

* Check for output exists before writting the file to avoid failure

* Added support for Asta Powerproject files
This commit is contained in:
HassanEmam
2021-10-31 23:52:43 +00:00
committed by GitHub
parent 208132785f
commit c8eb933453
7 changed files with 318 additions and 7 deletions
@@ -83,6 +83,7 @@ classes = (
operator.GenerateGanttChart,
operator.ImportP6,
operator.ImportP6XER,
operator.ImportPP,
operator.ImportMSP,
operator.LoadTaskProperties,
operator.SelectTaskRelatedProducts,
@@ -126,6 +127,7 @@ classes = (
def menu_func_import(self, context):
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
self.layout.operator(operator.ImportP6XER.bl_idname, text="P6 (.xer)")
self.layout.operator(operator.ImportPP.bl_idname, text="Powerproject (.pp)")
self.layout.operator(operator.ImportMSP.bl_idname, text="Microsoft Project (.xml)")
@@ -1120,6 +1120,27 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
return {"FINISHED"}
class ImportPP(bpy.types.Operator, ImportHelper):
bl_idname = "import_pp.bim"
bl_label = "Import Powerproject pp"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".pp"
filter_glob: bpy.props.StringProperty(default="*.pp", options={"HIDDEN"})
def execute(self, context):
from ifc4d.pp2ifc import PP2Ifc
self.file = IfcStore.get_file()
start = time.time()
pp2ifc = PP2Ifc()
pp2ifc.pp = self.filepath
pp2ifc.file = self.file
pp2ifc.work_plan = self.file.by_type("IfcWorkPlan")[0] if self.file.by_type("IfcWorkPlan") else None
pp2ifc.execute()
Data.load(IfcStore.get_file())
print("Import finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"}
class ImportMSP(bpy.types.Operator, ImportHelper):
bl_idname = "import_msp.bim"
bl_label = "Import MSP"
@@ -2,9 +2,10 @@ import os
import sys
import argparse
from p6xer2ifc import P6XER2Ifc
from p62ifc import P62Ifc
from msp2ifc import MSP2Ifc
from ifc4d.p6xer2ifc import P6XER2Ifc
from ifc4d.p62ifc import P62Ifc
from ifc4d.msp2ifc import MSP2Ifc
from ifc4d.pp2ifc import PP2Ifc
import ifcopenshell
@@ -12,7 +13,7 @@ parser = argparse.ArgumentParser()
parser .add_argument('-f','--file', action='store', type=str,
required=True, help="schedule file name to be parsed")
parser .add_argument('-s','--schedule', action='store', required=True,
type=str, help='file format as xer, p6xml, mspxml')
type=str, help='file format as xer, p6xml, mspxml, pp')
parser .add_argument('-i', '--ifcfile', action='store', required=False,
type=str, help='ifc file name as string e.g. \"file.ifc\"')
parser .add_argument('-o', '--output', action='store', required=True,
@@ -65,6 +66,14 @@ elif args.schedule == "p6xml":
if ifcfile:
p6xml.file = ifcopenshell.open(args.ifcfile)
p6xml.execute()
elif args.schedule == "pp":
pp = PP2Ifc()
pp.output = args.output
pp.pp = args.file
ifcfile = get_file()
if ifcfile:
pp.file = ifcopenshell.open(args.ifcfile)
pp.execute()
else:
print("schedule type you selected is not implemented at the moment")
+3 -3
View File
@@ -200,12 +200,12 @@ class ScheduleIfcGenerator:
identification = wbs["Code"]
if wbs["ParentObjectId"]:
if self.wbs[wbs["ParentObjectId"]]["ifc"]:
identification = self.wbs[wbs["ParentObjectId"]]["ifc"].Identification + "." + wbs["Code"]
identification = str(self.wbs[wbs["ParentObjectId"]]["ifc"].Identification) + "." + str(wbs["Code"])
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
task=wbs["ifc"],
attributes={"Name": wbs["Name"], "Identification": identification},
attributes={"Name": wbs["Name"], "Identification": str(identification)},
)
for activity_id in wbs["activities"]:
self.create_task_from_activity(self.activities[activity_id], wbs, None)
@@ -223,7 +223,7 @@ class ScheduleIfcGenerator:
task=activity["ifc"],
attributes={
"Name": activity["Name"],
"Identification": activity["Identification"],
"Identification": str(activity["Identification"]),
"Status": activity["Status"],
"IsMilestone": activity["StartDate"] == activity["FinishDate"],
"PredefinedType": "CONSTRUCTION"
+1
View File
@@ -193,3 +193,4 @@ class P6XER2Ifc():
# TODO: consider showing progress bar for better user experience
# TODO: support multiple projects in a single file
# TODO: prompt user to select activities and/or wbs nodes to import instead of the full project
+217
View File
@@ -0,0 +1,217 @@
print("In module products __package__, __name__ ==", __package__)
print(__name__)
import sys
print("In module products sys.path[0], __package__ ==", sys.path[0], __package__)
sys.path.append(sys.path[0])
import sqlite3
import math
import datetime
from datetime import timedelta
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.date
from .wpattern import AstaCalendarWorkPattern
from .common import ScheduleIfcGenerator
import time
list_of_tables = [
'BAR',
'EXPANDED_TASK',
'PERMANENT_RESOURCE',
'CONSUMABLE_RESOURCE',
'TASK',
'PROJECT_SUMMARY',
'WBS_ENTRY'
]
class PP2Ifc:
def __init__(self):
self.pp = None
self.file = None
self.work_plan = None
self.project = {}
self.calendars = {}
self.wbs = {}
self.root_activites = []
self.activities = {}
self.relationships = {}
self.resources = {}
self.output = None
self.day_map = {
"Monday": 1,
"Tuesday": 2,
"Wednesday": 3,
"Thursday": 4,
"Friday": 5,
"Saturday": 6,
"Sunday": 7,
}
self.relationship_map = {
0: "FINISH_START",
1: "FINISH_FINISH",
2: "START_START",
3: "START_FINISH"
}
def get_json(self, table_name):
self.cur.execute("select * from " + table_name)
r = [dict((self.cur.description[i][0], value) for i, value in enumerate(row)) for row in self.cur.fetchall()]
return r
def get_json_with_filter(self, table_name, attr_name, attr_value):
self.cur.execute("select * from " + table_name + " where " + attr_name + " = " + str(attr_value))
r = [dict((self.cur.description[i][0], value) for i, value in enumerate(row)) for row in self.cur.fetchall()]
return r
def execute(self):
self.con = sqlite3.connect(self.pp)
self.cur = self.con.cursor()
self.parse_pp()
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
}
start = time.time()
ifcCreator = ScheduleIfcGenerator(self.file, self.output, settings)
end = time.time()
ifcCreator.create_ifc()
end2 = time.time()
print("Parsing time is", end - start)
print("IFC Creation took", end2 - end)
print("Overall Time", end2 - start)
# self.create_ifc()
def parse_pp(self):
project = self.get_json("PROJECT_SUMMARY")[0]
self.project["Name"] = project["SHORT_NAME"]
self.parse_calendar_pp()
self.parse_wbs_pp()
self.parse_activity_pp()
self.parse_relationship_pp(project)
def parse_calendar_pp(self):
calendars = self.get_json("CALENDAR")
wp_data = self.get_json("WORK_PATTERN")
for calendar in calendars:
calendar_id = calendar["ID"]
calendar_wp = calendar["DOMINANT_WORK_PATTERN"]
wp_data = self.get_json_with_filter("WORK_PATTERN", "ID", calendar_wp)
print("wp_data", wp_data[0]['SHIFTS'])
wp = AstaCalendarWorkPattern(wp_data[0]['SHIFTS'])
print(wp.dict_wp)
exceptions = {}
timex = []
for times in wp.dict_wp:
work_times = timedelta(hours=0)
for daily in times['WorkTimes']:
fin = daily['Finish']
strt = daily['Start']
work_times += timedelta(hours=fin.hour, minutes=fin.minute) - timedelta(hours=strt.hour, minutes=strt.minute)
timex.append(work_times.total_seconds() /(60*60))
self.calendars[calendar_id] = {
"Name": calendar["NAME"],
"Type": "NOTDEFINED",
"HoursPerDay": max(timex),
"StandardWorkWeek": wp.dict_wp,
"HolidayOrExceptions": exceptions,
}
#print(self.calendars[calendar_id])
def parse_wbs_pp(self):
bars = self.get_json("BAR")
extended_tasks = self.get_json("EXPANDED_TASK")
for bar in bars:
self.wbs[bar["ID"]] = {
"Name": bar["NAME"],
"Code": bar["ID"],
"ParentObjectId": bar["EXPANDED_TASK"] if bar["EXPANDED_TASK"]> 0 else None,
"ifc": None,
"rel": None,
"activities": [],
}
for bar in extended_tasks:
self.wbs[bar["ID"]] = {
"Name": bar["NAME"],
"Code": bar["ID"],
"ParentObjectId": bar["BAR"] if bar["BAR"]> 0 else None,
"ifc": None,
"rel": None,
"activities": [],
}
#print(self.wbs)
def parse_activity_pp(self):
activities = self.get_json("TASK")
milestones = self.get_json("MILESTONE")
for activity in activities:
activity_type = "TASK"
activity_id = activity["ID"]
wbs_id = activity["BAR"]
if wbs_id:
self.wbs[wbs_id]["activities"].append(activity_id)
else:
self.root_activites.append(activity_id)
self.activities[activity_id] = {
"Name": activity["NAME"],
"Identification": activity["ID"],
"StartDate": datetime.datetime.fromisoformat(activity["LINKABLE_START"]),
"FinishDate": datetime.datetime.fromisoformat(activity["LINKABLE_FINISH"]),
"PlannedDuration": float(activity["PLANNED_DURATION"].split(",")[-2].replace("<","").replace(">","") ),
"Status": "PLANNED",
"CalendarObjectId": activity["CALENDAR"],
"ifc": None,
}
for activity in milestones:
activity_type = "MILESTONE"
activity_id = activity["ID"]
wbs_id = activity["BAR"]
if wbs_id:
self.wbs[wbs_id]["activities"].append(activity_id)
else:
self.root_activites.append(activity_id)
self.activities[activity_id] = {
"Name": activity["NAME"],
"Identification": activity["ID"],
"StartDate": datetime.datetime.fromisoformat(activity["LINKABLE_START"]),
"FinishDate": datetime.datetime.fromisoformat(activity["LINKABLE_FINISH"]),
"PlannedDuration": 0.0,
"Status": "PLANNED",
"CalendarObjectId": activity["CALENDAR"],
"ifc": None,
}
#print("***ACTIVITIES", self.activities[activity_id])
def parse_relationship_pp(self, project):
relations = self.get_json('LINK')
for relationship in relations:
predecessor = relationship['START_TASK']
successor = relationship['END_TASK']
if predecessor not in self.activities or successor not in self.activities:
print("!!!!!!!!!!!!!!!!!ERROR!!!!!!!!!!!!!!!!")
print(predecessor, successor)
continue
self.relationships[relationship["ID"]] = {
"PredecessorActivity": predecessor,
"SuccessorActivity": successor,
"Type": self.relationship_map[relationship['LINK_KIND']],
"Lag": float(relationship['END_LAG_TIME'].split(",")[-2].replace("<","").replace(">","")),
}
+61
View File
@@ -0,0 +1,61 @@
"""
This class parses the calendar work pattern and retruns a list
The list returned has a key DayOfWeek which takes a value Sunday to Saturday
for each day as a key there is a list of working times with the format
{"Start": datetime.time, "Finish": datetime.time}
"""
import re
from datetime import time, datetime
class AstaCalendarWorkPattern:
def get_keys(self, s):
regex = r"\<(\".+?\")\>(\w|\d|)+?"
matches=re.finditer(regex, s)
matcs = []
for matchNum, match in enumerate(matches, start=1):
matcs.append(match.group(1))
return matcs
def get_values(self, s):
rx2 = r"<\"[^<>]+\">"
data = re.split('<[^<>]+>', s)
return data
def __init__(self, string):
self.string = string
self.Days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
self.keys = self.get_keys(string)
self.values = self.get_values(string)
self.dict_wp = {}
self.dict_wp = []
for d, m in zip(self.values[1:], self.keys):
splt_data = d.strip().split(",")
workhours = []
if len(splt_data)>15:
print(splt_data)
st1_1 = datetime.strptime(splt_data[6], "%H%M%S")
st1_2 = datetime.strptime(splt_data[7], "%H%M%S")
st = {"Start":time(st1_1.hour, st1_1.minute), "Finish": time(st1_2.hour, st1_2.minute), "ifc": None}
workhours.append(st)
st2_1 = datetime.strptime(splt_data[12], "%H%M%S")
st2_2 = datetime.strptime(splt_data[13], "%H%M%S")
st2 = {"Start":time(st2_1.hour, st2_1.minute), "Finish": time(st2_2.hour, st2_2.minute), "ifc": None}
workhours.append(st2)
self.dict_wp.append({'DayOfWeek': m.replace("\"",""),
'WorkTimes': workhours, "ifc": None})
for day in self.Days:
if not len(list(filter(lambda d: d['DayOfWeek']== day , self.dict_wp))) > 0:
print("MISSING", day)
self.dict_wp.append({'DayOfWeek': day,
'WorkTimes': [], "ifc": None})
print("final", self.dict_wp)