mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 14:48:02 +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.GenerateGanttChart,
|
||||||
operator.ImportP6,
|
operator.ImportP6,
|
||||||
operator.ImportP6XER,
|
operator.ImportP6XER,
|
||||||
|
operator.ImportPP,
|
||||||
operator.ImportMSP,
|
operator.ImportMSP,
|
||||||
operator.LoadTaskProperties,
|
operator.LoadTaskProperties,
|
||||||
operator.SelectTaskRelatedProducts,
|
operator.SelectTaskRelatedProducts,
|
||||||
@@ -126,6 +127,7 @@ classes = (
|
|||||||
def menu_func_import(self, context):
|
def menu_func_import(self, context):
|
||||||
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
|
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
|
||||||
self.layout.operator(operator.ImportP6XER.bl_idname, text="P6 (.xer)")
|
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)")
|
self.layout.operator(operator.ImportMSP.bl_idname, text="Microsoft Project (.xml)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1120,6 +1120,27 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
|
|||||||
return {"FINISHED"}
|
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):
|
class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||||
bl_idname = "import_msp.bim"
|
bl_idname = "import_msp.bim"
|
||||||
bl_label = "Import MSP"
|
bl_label = "Import MSP"
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from p6xer2ifc import P6XER2Ifc
|
from ifc4d.p6xer2ifc import P6XER2Ifc
|
||||||
from p62ifc import P62Ifc
|
from ifc4d.p62ifc import P62Ifc
|
||||||
from msp2ifc import MSP2Ifc
|
from ifc4d.msp2ifc import MSP2Ifc
|
||||||
|
from ifc4d.pp2ifc import PP2Ifc
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ parser = argparse.ArgumentParser()
|
|||||||
parser .add_argument('-f','--file', action='store', type=str,
|
parser .add_argument('-f','--file', action='store', type=str,
|
||||||
required=True, help="schedule file name to be parsed")
|
required=True, help="schedule file name to be parsed")
|
||||||
parser .add_argument('-s','--schedule', action='store', required=True,
|
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,
|
parser .add_argument('-i', '--ifcfile', action='store', required=False,
|
||||||
type=str, help='ifc file name as string e.g. \"file.ifc\"')
|
type=str, help='ifc file name as string e.g. \"file.ifc\"')
|
||||||
parser .add_argument('-o', '--output', action='store', required=True,
|
parser .add_argument('-o', '--output', action='store', required=True,
|
||||||
@@ -65,6 +66,14 @@ elif args.schedule == "p6xml":
|
|||||||
if ifcfile:
|
if ifcfile:
|
||||||
p6xml.file = ifcopenshell.open(args.ifcfile)
|
p6xml.file = ifcopenshell.open(args.ifcfile)
|
||||||
p6xml.execute()
|
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:
|
else:
|
||||||
print("schedule type you selected is not implemented at the moment")
|
print("schedule type you selected is not implemented at the moment")
|
||||||
|
|
||||||
@@ -200,12 +200,12 @@ class ScheduleIfcGenerator:
|
|||||||
identification = wbs["Code"]
|
identification = wbs["Code"]
|
||||||
if wbs["ParentObjectId"]:
|
if wbs["ParentObjectId"]:
|
||||||
if self.wbs[wbs["ParentObjectId"]]["ifc"]:
|
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(
|
ifcopenshell.api.run(
|
||||||
"sequence.edit_task",
|
"sequence.edit_task",
|
||||||
self.file,
|
self.file,
|
||||||
task=wbs["ifc"],
|
task=wbs["ifc"],
|
||||||
attributes={"Name": wbs["Name"], "Identification": identification},
|
attributes={"Name": wbs["Name"], "Identification": str(identification)},
|
||||||
)
|
)
|
||||||
for activity_id in wbs["activities"]:
|
for activity_id in wbs["activities"]:
|
||||||
self.create_task_from_activity(self.activities[activity_id], wbs, None)
|
self.create_task_from_activity(self.activities[activity_id], wbs, None)
|
||||||
@@ -223,7 +223,7 @@ class ScheduleIfcGenerator:
|
|||||||
task=activity["ifc"],
|
task=activity["ifc"],
|
||||||
attributes={
|
attributes={
|
||||||
"Name": activity["Name"],
|
"Name": activity["Name"],
|
||||||
"Identification": activity["Identification"],
|
"Identification": str(activity["Identification"]),
|
||||||
"Status": activity["Status"],
|
"Status": activity["Status"],
|
||||||
"IsMilestone": activity["StartDate"] == activity["FinishDate"],
|
"IsMilestone": activity["StartDate"] == activity["FinishDate"],
|
||||||
"PredefinedType": "CONSTRUCTION"
|
"PredefinedType": "CONSTRUCTION"
|
||||||
|
|||||||
@@ -193,3 +193,4 @@ class P6XER2Ifc():
|
|||||||
# TODO: consider showing progress bar for better user experience
|
# TODO: consider showing progress bar for better user experience
|
||||||
# TODO: support multiple projects in a single file
|
# 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
|
# 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