mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
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:
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(">","")),
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user