Introdução
Para gerar o gráfico de Gantt conectado aos nossos modelos, precisamos de uma biblioteca e uma forma de implementar informações adicionais relacionadas às tarefas que cada elemento fará parte.
Para a biblioteca, escolhemos Frappe/Gantt pela sua simplicidade e flexibilidade.
Esta biblioteca requer o seguinte objeto para definir uma tarefa:
{
id: 'Task id',
name: 'Task name',
start: 'Task start',
end: 'Task end',
progress: Task_progress,
dependencies: 'Tasks dependencies'
}
Precisamos de todas essas informações para gerar cada tarefa e também precisamos de uma maneira de mapear as tarefas e os elementos do nosso modelo. Neste exemplo, usaremos uma propriedade para fazer esse mapeamento (controlado pelo arquivo de configuração config.js
).
Ao final, teremos a seguinte configuração para nosso csv:
ID | NAME | START | END | PROGRESS | Type Name | DEPENDENCIES |
---|---|---|---|---|---|---|
123 | Timber Floors | 2022-03-10 | 2022-04-04 | 100 | Timber Suspended Floor | 121-122 |
Com este csv temos todas as informações exigidas pela biblioteca e a propriedade utilizada para conectar com o modelo (Type Name).
Você pode encontrar um exemplo de input csv aqui
Também precisamos do arquivo de configuração (config.js
). A configuração contém informações que usaremos em diferentes seções deste tutorial e abordaremos cada parte aqui. Por enquanto, vamos nos concentrar na propriedade usada para mapear tarefas e elementos. Isso é controlado pelo campo propFilter
.
Por enquanto, você pode criar um arquivo config.js
na pasta wwwroot/extensions
com o conteúdo abaixo:
export const phasing_config = {
propFilter: "Type Name",
};
Teremos uma ideia de como isso vai funcionar no decorrer do tutorial, pois continuaremos melhorando o config.js
e nossa extensão.
Como dito na página inicial, você também precisará do arquivo BaseExtension.js
para que este exemplo funcione. Adicione o arquivo na pasta extensions
do seu projeto com o conteúdo abaixo.
export class BaseExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this._onObjectTreeCreated = (ev) => this.onModelLoaded(ev.model);
this._onSelectionChanged = (ev) =>
this.onSelectionChanged(ev.model, ev.dbIdArray);
this._onIsolationChanged = (ev) =>
this.onIsolationChanged(ev.model, ev.nodeIdArray);
}
load() {
this.viewer.addEventListener(
Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT,
this._onObjectTreeCreated
);
this.viewer.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
this._onSelectionChanged
);
this.viewer.addEventListener(
Autodesk.Viewing.ISOLATE_EVENT,
this._onIsolationChanged
);
return true;
}
unload() {
this.viewer.removeEventListener(
Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT,
this._onObjectTreeCreated
);
this.viewer.removeEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
this._onSelectionChanged
);
this.viewer.removeEventListener(
Autodesk.Viewing.ISOLATE_EVENT,
this._onIsolationChanged
);
return true;
}
onModelLoaded(model) {}
onSelectionChanged(model, dbids) {}
onIsolationChanged(model, dbids) {}
findLeafNodes(model) {
return new Promise(function (resolve, reject) {
model.getObjectTree(function (tree) {
let leaves = [];
tree.enumNodeChildren(
tree.getRootId(),
function (dbid) {
if (tree.getChildCount(dbid) === 0) {
leaves.push(dbid);
}
},
true
);
resolve(leaves);
}, reject);
});
}
async findPropertyNames(model) {
const dbids = await this.findLeafNodes(model);
return new Promise(function (resolve, reject) {
model.getBulkProperties(
dbids,
{},
function (results) {
let propNames = new Set();
for (const result of results) {
for (const prop of result.properties) {
propNames.add(prop.displayName);
}
}
resolve(Array.from(propNames.values()));
},
reject
);
});
}
createToolbarButton(buttonId, buttonIconUrl, buttonTooltip) {
let group = this.viewer.toolbar.getControl("dashboard-toolbar-group");
if (!group) {
group = new Autodesk.Viewing.UI.ControlGroup("dashboard-toolbar-group");
this.viewer.toolbar.addControl(group);
}
const button = new Autodesk.Viewing.UI.Button(buttonId);
button.setToolTip(buttonTooltip);
group.addControl(button);
const icon = button.container.querySelector(".adsk-button-icon");
if (icon) {
icon.style.backgroundImage = `url(${buttonIconUrl})`;
icon.style.backgroundSize = `24px`;
icon.style.backgroundRepeat = `no-repeat`;
icon.style.backgroundPosition = `center`;
}
return button;
}
removeToolbarButton(button) {
const group = this.viewer.toolbar.getControl("dashboard-toolbar-group");
group.removeControl(button);
}
loadScript(url, namespace) {
if (window[namespace] !== undefined) {
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
const el = document.createElement("script");
el.src = url;
el.onload = resolve;
el.onerror = reject;
document.head.appendChild(el);
});
}
loadStylesheet(url) {
return new Promise(function (resolve, reject) {
const el = document.createElement("link");
el.rel = "stylesheet";
el.href = url;
el.onload = resolve;
el.onerror = reject;
document.head.appendChild(el);
});
}
}
Para gerenciar os inputs (CSV) no nosso exemplo, estamos usando a biblioteca SweetAlert2. Para usá-la basta adicionar sua referência copiando a linha abaixo no arquivo index.html
:
...
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
========START OF THE ADDITIONAL CONTENT========
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
========END OF THE ADDITIONAL CONTENT========
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.css">
<title>Autodesk Platform Services: Construction Pasing Sample</title>
</head>
...
Agora que sabemos como vai funcionar e as dependências do projeto, podemos começar a construir nosso exemplo.