mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
BBIM 4D: You can now visualize & print work schedule baselines with Gantt Charts
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
var json_data = `{{{json_data}}}`;
|
var json_data = `{{{json_data}}}`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="no-print">
|
<div id="options" class="no-print">
|
||||||
<h4>Choose a language:
|
<h4>Choose a language:
|
||||||
<select id="lang" class="sweet_button" onchange="set_language(event)">
|
<select id="lang" class="sweet_button" onchange="set_language(event)">
|
||||||
<option value='cn'>Chinese (cn)</option>
|
<option value='cn'>Chinese (cn)</option>
|
||||||
@@ -25,29 +25,31 @@ var json_data = `{{{json_data}}}`;
|
|||||||
<option value='sv'>Swedish (sv)</option>
|
<option value='sv'>Swedish (sv)</option>
|
||||||
<option value='tr'>Turkish (tr)</option>
|
<option value='tr'>Turkish (tr)</option>
|
||||||
</select>
|
</select>
|
||||||
<h4>
|
</h4>
|
||||||
<div id="print_options">
|
|
||||||
<select class="sweet_button" id="print_page_size">
|
|
||||||
<option value="210,297">A4 Portrait</option>
|
|
||||||
<option value="297,210">A4 Landscape</option>
|
|
||||||
<option value="297,420">A3 Portrait</option>
|
|
||||||
<option value="420,297">A3 Landscape</option>
|
|
||||||
<option value="420,594">A2 Portrait</option>
|
|
||||||
<option value="594,420">A2 Landscape</option>
|
|
||||||
<option value="594,841">A1 Portrait</option>
|
|
||||||
<option value="841,594">A1 Landscape</option>
|
|
||||||
<option value="841,1189">A0 Portrait</option>
|
|
||||||
<option value="1189,841">A0 Landscape</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="top-right no-print" id="print_options">
|
||||||
|
<select class="sweet_button" id="print_page_size">
|
||||||
|
<option value="210,297">A4 Portrait</option>
|
||||||
|
<option value="297,210">A4 Landscape</option>
|
||||||
|
<option value="297,420">A3 Portrait</option>
|
||||||
|
<option value="420,297">A3 Landscape</option>
|
||||||
|
<option value="420,594">A2 Portrait</option>
|
||||||
|
<option value="594,420">A2 Landscape</option>
|
||||||
|
<option value="594,841">A1 Portrait</option>
|
||||||
|
<option value="841,594">A1 Landscape</option>
|
||||||
|
<option value="841,1189">A0 Portrait</option>
|
||||||
|
<option value="1189,841">A0 Landscape</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="schedule-data"></div>
|
<div id="schedule-data" class="no-print"></div>
|
||||||
|
<div id="schedule-header" style=""></div>
|
||||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var name = `{{{name}}}`;
|
var data = `{{{data}}}`;
|
||||||
setupPage(name);
|
setupPage(data);
|
||||||
create_gantt_chart(json_data)
|
create_gantt_chart(json_data)
|
||||||
</script>
|
</script>
|
||||||
@@ -23,6 +23,7 @@ function create_gantt_chart(json_data) {
|
|||||||
JSGantt.parseJSONString(json_data, g);
|
JSGantt.parseJSONString(json_data, g);
|
||||||
g.Draw();
|
g.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_language(e){
|
function set_language(e){
|
||||||
lang = e && e.target ? e.target.value : "en";
|
lang = e && e.target ? e.target.value : "en";
|
||||||
g.setOptions({
|
g.setOptions({
|
||||||
@@ -31,7 +32,27 @@ function set_language(e){
|
|||||||
g.Draw();
|
g.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupPage(name){
|
|
||||||
|
function draw_table(json_object){
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.classList.add("table-description");
|
||||||
|
for (let key in json_object){
|
||||||
|
let row = document.createElement("tr");
|
||||||
|
let key_cell = document.createElement("td");
|
||||||
|
let key_text = document.createTextNode(key);
|
||||||
|
key_cell.appendChild(key_text);
|
||||||
|
row.appendChild(key_cell);
|
||||||
|
let value_cell = document.createElement("td");
|
||||||
|
let value_text = document.createTextNode(json_object[key]);
|
||||||
|
value_cell.appendChild(value_text);
|
||||||
|
row.appendChild(value_cell);
|
||||||
|
table.appendChild(row);
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPage(workScheduleData){
|
||||||
|
workScheduleData = JSON.parse(workScheduleData);
|
||||||
let print_button = document.createElement("button");
|
let print_button = document.createElement("button");
|
||||||
print_button.id = "print-schedule";
|
print_button.id = "print-schedule";
|
||||||
print_button.innerHTML = "Print";
|
print_button.innerHTML = "Print";
|
||||||
@@ -40,18 +61,37 @@ function setupPage(name){
|
|||||||
g.Draw();
|
g.Draw();
|
||||||
var values = document.getElementById("print_page_size").value.split(",")
|
var values = document.getElementById("print_page_size").value.split(",")
|
||||||
let css =
|
let css =
|
||||||
"@media print {\n @page {\n size: " + values[0] + "mm " + values[1] + "mm;\n }\n /* set gantt container to the same width as the page */\n .gantt {\n width: " + values[0] + "mm;\n }\n };";
|
"@media print {\n @page {\n size: " + values[0] + "mm " + values[1] + "mm;\n }\n";
|
||||||
g.printChart(values[0], values[1], css);
|
g.printChart(values[0], values[1], css);
|
||||||
g.setTotalHeight(900);
|
g.setTotalHeight(900);
|
||||||
g.Draw();
|
g.Draw();
|
||||||
});
|
});
|
||||||
document.getElementById("print_options").appendChild(print_button);
|
document.getElementById("print_options").appendChild(print_button);
|
||||||
|
|
||||||
// add a nice layout to show the schedule name
|
let table = draw_table(workScheduleData)
|
||||||
let schedule_name = document.createElement("h1");
|
document.getElementById("schedule-data").appendChild(table);
|
||||||
// create string to say "Schedule: <name>"
|
table.style.display = "none";
|
||||||
let schedule_name_string = document.createTextNode("Schedule: " + name);
|
let toggle_button = document.createElement("button");
|
||||||
// add the string to the h1 element
|
toggle_button.id = "toggle-table";
|
||||||
|
toggle_button.innerHTML = "Show Schedule Information";
|
||||||
|
toggle_button.onclick = function(){
|
||||||
|
if (table.style.display === "none"){
|
||||||
|
table.style.display = "table";
|
||||||
|
toggle_button.innerHTML = "Hide Schedule Information";
|
||||||
|
} else {
|
||||||
|
toggle_button.innerHTML = "Show Schedule Information";
|
||||||
|
table.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("options").appendChild(toggle_button);
|
||||||
|
|
||||||
|
let schedule_name = document.createElement("h2");
|
||||||
|
let schedule_name_string = document.createTextNode("Schedule: " + workScheduleData.Name);
|
||||||
schedule_name.appendChild(schedule_name_string);
|
schedule_name.appendChild(schedule_name_string);
|
||||||
document.getElementById("schedule-data").appendChild(schedule_name);
|
document.getElementById("schedule-header").appendChild(schedule_name);
|
||||||
|
|
||||||
|
let creation_date = document.createElement("h2");
|
||||||
|
let creation_date_string = document.createTextNode("Created: " + new Date(workScheduleData.CreationDate).toLocaleDateString());
|
||||||
|
creation_date.appendChild(creation_date_string);
|
||||||
|
document.getElementById("schedule-header").appendChild(creation_date);
|
||||||
}
|
}
|
||||||
@@ -24,11 +24,6 @@
|
|||||||
background: #D4B483;
|
background: #D4B483;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gmainleft {
|
|
||||||
overflow: visible;
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gname.ggroupitem {
|
.gname.ggroupitem {
|
||||||
background-color: #4c92ec;
|
background-color: #4c92ec;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -63,8 +58,7 @@
|
|||||||
.gmainleft {
|
.gmainleft {
|
||||||
resize: horizontal;
|
resize: horizontal;
|
||||||
height:100%;
|
height:100%;
|
||||||
overflow-x: visible;
|
/* flex: 0 1 auto; */
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gmainright {
|
.gmainright {
|
||||||
@@ -113,10 +107,66 @@ button, .sweet_button {
|
|||||||
box-shadow: 0 0 0.7rem rgba(95, 95, 95, 0.4);
|
box-shadow: 0 0 0.7rem rgba(95, 95, 95, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: black;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: black;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
color: black;
|
color: black;
|
||||||
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-description {
|
||||||
|
/* create a github style table similar to a wild car */
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width : 50%;
|
||||||
|
background-color: transparent;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-color: grey;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
box-shadow: 0 0 0.7rem rgba(95, 95, 95, 0.4);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-description tr {
|
||||||
|
display: table-row;
|
||||||
|
vertical-align: inherit;
|
||||||
|
border-color: inherit;
|
||||||
|
border-bottom: 1px solid #e1e1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-description thead {
|
||||||
|
display: table-header-group;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-color: inherit;
|
||||||
|
background-color: #95A5A6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#schedule-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
@@ -485,10 +485,13 @@ class BIM_PT_work_schedules(Panel):
|
|||||||
baseline_row.label(
|
baseline_row.label(
|
||||||
text="{} @ {}".format(baseline["name"], baseline["date"]), icon="RESTRICT_INSTANCED_OFF"
|
text="{} @ {}".format(baseline["name"], baseline["date"]), icon="RESTRICT_INSTANCED_OFF"
|
||||||
)
|
)
|
||||||
baseline_row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = baseline["id"]
|
baseline_row.operator("bim.generate_gantt_chart", text="Compare", icon="NLA").work_schedule = baseline[
|
||||||
|
"id"
|
||||||
|
]
|
||||||
baseline_row.operator(
|
baseline_row.operator(
|
||||||
"bim.enable_editing_work_schedule_tasks", text="", icon="ACTION"
|
"bim.enable_editing_work_schedule_tasks", text="", icon="ACTION"
|
||||||
).work_schedule = baseline["id"]
|
).work_schedule = baseline["id"]
|
||||||
|
baseline_row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = baseline["id"]
|
||||||
|
|
||||||
def draw_readonly_work_schedule_ui(self, work_schedule_id):
|
def draw_readonly_work_schedule_ui(self, work_schedule_id):
|
||||||
if self.props.active_work_schedule_id == work_schedule_id:
|
if self.props.active_work_schedule_id == work_schedule_id:
|
||||||
@@ -515,6 +518,7 @@ class BIM_PT_work_schedules(Panel):
|
|||||||
"active_task_index",
|
"active_task_index",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_task_icom(Panel):
|
class BIM_PT_task_icom(Panel):
|
||||||
bl_label = "IFC Task ICOM"
|
bl_label = "IFC Task ICOM"
|
||||||
bl_idname = "BIM_PT_task_icom"
|
bl_idname = "BIM_PT_task_icom"
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ def visualise_work_schedule_date(sequence, work_schedule=None):
|
|||||||
|
|
||||||
def generate_gantt_chart(sequence, work_schedule):
|
def generate_gantt_chart(sequence, work_schedule):
|
||||||
json = sequence.create_tasks_json(work_schedule)
|
json = sequence.create_tasks_json(work_schedule)
|
||||||
sequence.generate_gantt_browser_chart(json)
|
sequence.generate_gantt_browser_chart(json, work_schedule)
|
||||||
|
|
||||||
|
|
||||||
def load_product_related_tasks(sequence, product=None):
|
def load_product_related_tasks(sequence, product=None):
|
||||||
|
|||||||
@@ -1171,7 +1171,6 @@ class Sequence(blenderbim.core.tool.Sequence):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def show_snapshot(cls, product_states):
|
def show_snapshot(cls, product_states):
|
||||||
print(product_states)
|
|
||||||
bpy.context.scene.frame_start = 1
|
bpy.context.scene.frame_start = 1
|
||||||
bpy.context.scene.frame_end = 2
|
bpy.context.scene.frame_end = 2
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
@@ -1312,7 +1311,6 @@ class Sequence(blenderbim.core.tool.Sequence):
|
|||||||
def clear_object_animation(cls, obj):
|
def clear_object_animation(cls, obj):
|
||||||
if obj.animation_data:
|
if obj.animation_data:
|
||||||
obj.animation_data_clear()
|
obj.animation_data_clear()
|
||||||
print("Cleared animation for", obj.name)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_objects_animation(cls, include_blender_objects=True):
|
def clear_objects_animation(cls, include_blender_objects=True):
|
||||||
@@ -1475,32 +1473,60 @@ class Sequence(blenderbim.core.tool.Sequence):
|
|||||||
"USERDEFINED": "FS",
|
"USERDEFINED": "FS",
|
||||||
"NOTDEFINED": "FS",
|
"NOTDEFINED": "FS",
|
||||||
}
|
}
|
||||||
|
is_baseline = False
|
||||||
|
if work_schedule.PredefinedType == "BASELINE":
|
||||||
|
is_baseline = True
|
||||||
tasks_json = []
|
tasks_json = []
|
||||||
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
||||||
cls.create_new_task_json(task, tasks_json, sequence_type_map)
|
cls.create_new_task_json(task, tasks_json, sequence_type_map, is_baseline=is_baseline)
|
||||||
return tasks_json
|
return tasks_json
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_new_task_json(cls, task, json, type_map=None):
|
def create_new_task_json(cls, task, json, type_map=None, is_baseline=False):
|
||||||
task_time = task.TaskTime
|
task_time = task.TaskTime
|
||||||
|
resources = ifcopenshell.util.sequence.get_task_resources(task, is_deep=False)
|
||||||
|
|
||||||
|
string_resources = ""
|
||||||
|
resources_usage = ""
|
||||||
|
for resource in resources:
|
||||||
|
string_resources += resource.Name + ", "
|
||||||
|
resources_usage += str(resource.Usage.ScheduleUsage) + ", " if resource.Usage else "-, "
|
||||||
|
|
||||||
|
schedule_start = task_time.ScheduleStart if task_time else ""
|
||||||
|
schedule_finish = task_time.ScheduleFinish if task_time else ""
|
||||||
|
|
||||||
|
declared_by_task = task.IsDeclaredBy[0].RelatingObject if is_baseline else None
|
||||||
|
if declared_by_task and declared_by_task.TaskTime:
|
||||||
|
compare_start = declared_by_task.TaskTime.ScheduleStart
|
||||||
|
compare_finish = declared_by_task.TaskTime.ScheduleFinish
|
||||||
|
else:
|
||||||
|
compare_start = schedule_start
|
||||||
|
compare_finish = schedule_finish
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"pID": task.id(),
|
"pID": task.id(),
|
||||||
"pName": task.Name,
|
"pName": task.Name,
|
||||||
"pCaption": task.Name,
|
"pCaption": task.Name,
|
||||||
"pStart": task_time.ScheduleStart if task_time else "",
|
"pStart": declared_by_task.TaskTime.ScheduleStart
|
||||||
"pEnd": task_time.ScheduleFinish if task_time else "",
|
if declared_by_task and declared_by_task.TaskTime
|
||||||
"pPlanStart": task_time.ScheduleStart if task_time else "",
|
else schedule_start,
|
||||||
"pPlanEnd": task_time.ScheduleFinish if task_time else "",
|
"pEnd": declared_by_task.TaskTime.ScheduleFinish
|
||||||
|
if declared_by_task and declared_by_task.TaskTime
|
||||||
|
else schedule_finish,
|
||||||
|
"pPlanStart": schedule_start,
|
||||||
|
"pPlanEnd": schedule_finish,
|
||||||
"pMile": 1 if task.IsMilestone else 0,
|
"pMile": 1 if task.IsMilestone else 0,
|
||||||
|
"pRes": string_resources,
|
||||||
"pComp": 0,
|
"pComp": 0,
|
||||||
"pGroup": 1 if task.IsNestedBy else 0,
|
"pGroup": 1 if task.IsNestedBy else 0,
|
||||||
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
||||||
"pOpen": 1,
|
"pOpen": 1,
|
||||||
"pCost": 1,
|
"pCost": 1,
|
||||||
"ifcduration": str(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration)) if (task_time and task_time.ScheduleDuration) else "",
|
"ifcduration": str(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration))
|
||||||
|
if (task_time and task_time.ScheduleDuration)
|
||||||
|
else "",
|
||||||
|
"resourceUsage": resources_usage,
|
||||||
}
|
}
|
||||||
|
|
||||||
if task_time and task_time.IsCritical:
|
if task_time and task_time.IsCritical:
|
||||||
data["pClass"] = "gtaskred"
|
data["pClass"] = "gtaskred"
|
||||||
elif data["pGroup"]:
|
elif data["pGroup"]:
|
||||||
@@ -1515,14 +1541,17 @@ class Sequence(blenderbim.core.tool.Sequence):
|
|||||||
)
|
)
|
||||||
json.append(data)
|
json.append(data)
|
||||||
for nested_task in ifcopenshell.util.sequence.get_nested_tasks(task):
|
for nested_task in ifcopenshell.util.sequence.get_nested_tasks(task):
|
||||||
cls.create_new_task_json(nested_task, json, type_map)
|
cls.create_new_task_json(nested_task, json, type_map, is_baseline)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_gantt_browser_chart(cls, task_json):
|
def generate_gantt_browser_chart(cls, task_json, work_schedule):
|
||||||
active_work_schedule = cls.get_active_work_schedule()
|
|
||||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
|
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
|
||||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
|
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
|
||||||
f.write(pystache.render(t.read(), {"json_data": json.dumps(task_json), "name":active_work_schedule.Name or "Unnamed"}))
|
f.write(
|
||||||
|
pystache.render(
|
||||||
|
t.read(), {"json_data": json.dumps(task_json), "data": json.dumps(work_schedule.get_info())}
|
||||||
|
)
|
||||||
|
)
|
||||||
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
|
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user