BBIM 4D: You can now visualize & print work schedule baselines with Gantt Charts

This commit is contained in:
Sigma Dimensions
2023-05-03 04:33:16 +01:00
parent c761c7d5d9
commit cbb6878f51
6 changed files with 176 additions and 51 deletions
@@ -6,7 +6,7 @@
var json_data = `{{{json_data}}}`;
</script>
<div class="no-print">
<div id="options" class="no-print">
<h4>Choose a language:
<select id="lang" class="sweet_button" onchange="set_language(event)">
<option value='cn'>Chinese (cn)</option>
@@ -25,29 +25,31 @@ var json_data = `{{{json_data}}}`;
<option value='sv'>Swedish (sv)</option>
<option value='tr'>Turkish (tr)</option>
</select>
<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>
</h4>
<br>
</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>
<script>
var name = `{{{name}}}`;
setupPage(name);
var data = `{{{data}}}`;
setupPage(data);
create_gantt_chart(json_data)
</script>
@@ -23,6 +23,7 @@ function create_gantt_chart(json_data) {
JSGantt.parseJSONString(json_data, g);
g.Draw();
}
function set_language(e){
lang = e && e.target ? e.target.value : "en";
g.setOptions({
@@ -31,7 +32,27 @@ function set_language(e){
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");
print_button.id = "print-schedule";
print_button.innerHTML = "Print";
@@ -40,18 +61,37 @@ function setupPage(name){
g.Draw();
var values = document.getElementById("print_page_size").value.split(",")
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.setTotalHeight(900);
g.Draw();
});
document.getElementById("print_options").appendChild(print_button);
// add a nice layout to show the schedule name
let schedule_name = document.createElement("h1");
// create string to say "Schedule: <name>"
let schedule_name_string = document.createTextNode("Schedule: " + name);
// add the string to the h1 element
let table = draw_table(workScheduleData)
document.getElementById("schedule-data").appendChild(table);
table.style.display = "none";
let toggle_button = document.createElement("button");
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);
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;
}
.gmainleft {
overflow: visible;
flex: 0 1 auto;
}
.gname.ggroupitem {
background-color: #4c92ec;
font-weight: bold;
@@ -63,8 +58,7 @@
.gmainleft {
resize: horizontal;
height:100%;
overflow-x: visible;
flex: 0 1 auto;
/* flex: 0 1 auto; */
}
.gmainright {
@@ -113,10 +107,66 @@ button, .sweet_button {
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 {
font-size: 1.2rem;
font-weight: 500;
line-height: 1.25;
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(
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(
"bim.enable_editing_work_schedule_tasks", text="", icon="ACTION"
).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):
if self.props.active_work_schedule_id == work_schedule_id:
@@ -515,6 +518,7 @@ class BIM_PT_work_schedules(Panel):
"active_task_index",
)
class BIM_PT_task_icom(Panel):
bl_label = "IFC Task ICOM"
bl_idname = "BIM_PT_task_icom"
+1 -1
View File
@@ -543,7 +543,7 @@ def visualise_work_schedule_date(sequence, work_schedule=None):
def generate_gantt_chart(sequence, 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):
+44 -15
View File
@@ -1171,7 +1171,6 @@ class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def show_snapshot(cls, product_states):
print(product_states)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 2
for obj in bpy.data.objects:
@@ -1312,7 +1311,6 @@ class Sequence(blenderbim.core.tool.Sequence):
def clear_object_animation(cls, obj):
if obj.animation_data:
obj.animation_data_clear()
print("Cleared animation for", obj.name)
@classmethod
def clear_objects_animation(cls, include_blender_objects=True):
@@ -1475,32 +1473,60 @@ class Sequence(blenderbim.core.tool.Sequence):
"USERDEFINED": "FS",
"NOTDEFINED": "FS",
}
is_baseline = False
if work_schedule.PredefinedType == "BASELINE":
is_baseline = True
tasks_json = []
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
@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
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 = {
"pID": task.id(),
"pName": task.Name,
"pCaption": task.Name,
"pStart": task_time.ScheduleStart if task_time else "",
"pEnd": task_time.ScheduleFinish if task_time else "",
"pPlanStart": task_time.ScheduleStart if task_time else "",
"pPlanEnd": task_time.ScheduleFinish if task_time else "",
"pStart": declared_by_task.TaskTime.ScheduleStart
if declared_by_task and declared_by_task.TaskTime
else schedule_start,
"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,
"pRes": string_resources,
"pComp": 0,
"pGroup": 1 if task.IsNestedBy else 0,
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
"pOpen": 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:
data["pClass"] = "gtaskred"
elif data["pGroup"]:
@@ -1515,14 +1541,17 @@ class Sequence(blenderbim.core.tool.Sequence):
)
json.append(data)
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
def generate_gantt_browser_chart(cls, task_json):
active_work_schedule = cls.get_active_work_schedule()
def generate_gantt_browser_chart(cls, task_json, 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.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"))
@classmethod