@xeokit/xeokit-viewer
Version:
BIM viewer built on xeokit
1,420 lines (1,271 loc) • 61.7 kB
JavaScript
import {Controller} from "./Controller.js";
import {BusyModal} from "./BusyModal.js";
import {ResetAction} from "./toolbar/ResetAction.js";
import {FitAction} from "./toolbar/FitAction.js";
import {FirstPersonMode} from "./toolbar/FirstPersonMode.js";
import {HideTool} from "./toolbar/HideTool.js";
import {SelectionTool} from "./toolbar/SelectionTool.js";
import {QueryTool} from "./toolbar/QueryTool.js";
import {SectionTool} from "./toolbar/SectionTool.js";
import {NavCubeMode} from "./toolbar/NavCubeMode.js";
import {ModelsExplorer} from "./explorer/ModelsExplorer.js";
import {ObjectsExplorer} from "./explorer/ObjectsExplorer.js";
import {ClassesExplorer} from "./explorer/ClassesExplorer.js";
import {StoreysExplorer} from "./explorer/StoreysExplorer.js";
import {Viewer} from "@xeokit/xeokit-sdk/src/viewer/Viewer.js";
import {AmbientLight} from "@xeokit/xeokit-sdk/src/viewer/scene/lights/AmbientLight.js";
import {DirLight} from "@xeokit/xeokit-sdk/src/viewer/scene/lights/DirLight.js";
import {BCFViewpointsPlugin} from "@xeokit/xeokit-sdk/src/plugins/BCFViewpointsPlugin/BCFViewpointsPlugin.js";
import {ThreeDMode} from "./toolbar/ThreeDMode.js";
import {ObjectContextMenu} from "./contextMenus/ObjectContextMenu.js";
import {math} from "@xeokit/xeokit-sdk/src/viewer/scene/math/math.js";
import {CanvasContextMenu} from "./contextMenus/CanvasContextMenu.js";
const explorerTemplate = `<div class="xeokit-tabs">
<div class="xeokit-tab xeokit-modelsTab">
<a class="xeokit-tab-btn" href="#">Models</a>
<div class="xeokit-tab-content">
<div class="xeokit-btn-group">
<button type="button" class="xeokit-unloadAllModels xeokit-btn disabled" data-tippy-content="Unload all models">Unload all</button>
</div>
<div class="xeokit-models" ></div>
</div>
</div>
<div class="xeokit-tab xeokit-objectsTab">
<a class="xeokit-tab-btn disabled" href="#">Objects</a>
<div class="xeokit-tab-content">
<div class="xeokit-btn-group">
<button type="button" class="xeokit-showAllObjects xeokit-btn disabled" data-tippy-content="Show all objects">Show all</button>
<button type="button" class="xeokit-hideAllObjects xeokit-btn disabled" data-tippy-content="Hide all objects">Hide all</button>
</div>
<div class="xeokit-objects xeokit-tree-panel" ></div>
</div>
</div>
<div class="xeokit-tab xeokit-classesTab">
<a class="xeokit-tab-btn disabled" href="#">Classes</a>
<div class="xeokit-tab-content">
<div class="xeokit-btn-group">
<button type="button" class="xeokit-showAllClasses xeokit-btn disabled" data-tippy-content="Show all classes">Show all</button>
<button type="button" class="xeokit-hideAllClasses xeokit-btn disabled" data-tippy-content="Hide all classes">Hide all</button>
</div>
<div class="xeokit-classes xeokit-tree-panel" ></div>
</div>
</div>
<div class="xeokit-tab xeokit-storeysTab">
<a class="xeokit-tab-btn disabled" href="#">Storeys</a>
<div class="xeokit-tab-content">
<div class="xeokit-btn-group">
<button type="button" class="xeokit-showAllStoreys xeokit-btn disabled" data-tippy-content="Show all storeys">Show all</button>
<button type="button" class="xeokit-hideAllStoreys xeokit-btn disabled" data-tippy-content="Hide all storeys">Hide all</button>
</div>
<div class="xeokit-storeys xeokit-tree-panel"></div>
</div>
</div>
</div>`;
const toolbarTemplate = `<div class="xeokit-toolbar">
<!-- Reset button -->
<div class="xeokit-btn-group">
<button type="button" class="xeokit-reset xeokit-btn fa fa-home fa-2x disabled" data-tippy-content="Reset view"></button>
</div>
<!-- 3D Mode button -->
<div class="xeokit-btn-group" role="group">
<button type="button" class="xeokit-threeD xeokit-btn fa fa-cube fa-2x" data-tippy-content="Toggle 2D/3D"></button>
</div>
<!-- Fit button -->
<div class="xeokit-btn-group" role="group">
<button type="button" class="xeokit-fit xeokit-btn fa fa-crop fa-2x disabled" data-tippy-content="View fit"></button>
</div>
<!-- First Person mode button -->
<div class="xeokit-btn-group" role="group">
<button type="button" class="xeokit-firstPerson xeokit-btn fa fa-male fa-2x disabled" data-tippy-content="First person"></button>
</div>
<!-- Tools button group -->
<div class="xeokit-btn-group" role="group">
<!-- Hide tool button -->
<button type="button" class="xeokit-hide xeokit-btn fa fa-eraser fa-2x disabled" data-tippy-content="Hide objects"></button>
<!-- Select tool button -->
<button type="button" class="xeokit-select xeokit-btn fa fa-mouse-pointer fa-2x disabled" data-tippy-content="Select objects"></button>
<!-- Query tool button -->
<button type="button" class="xeokit-query xeokit-btn fa fa-info-circle fa-2x disabled" data-tippy-content="Query objects"></button>
<!-- Slice tool button -->
<button type="button" class="xeokit-section xeokit-btn fa fa-cut fa-2x disabled" data-tippy-content="Slice objects"></button>
</div>
</div>`;
function initTabs(containerElement) {
const tabsClass = 'xeokit-tabs';
const tabClass = 'xeokit-tab';
const tabButtonClass = 'xeokit-tab-btn';
const activeClass = 'active';
// Activates the chosen tab and deactivates the rest
function activateTab(chosenTabElement) {
let tabList = chosenTabElement.parentNode.querySelectorAll('.' + tabClass);
for (let i = 0; i < tabList.length; i++) {
let tabElement = tabList[i];
if (tabElement.isEqualNode(chosenTabElement)) {
tabElement.classList.add(activeClass)
} else {
tabElement.classList.remove(activeClass)
}
}
}
// Initialize each tabbed container
let tabbedContainers = containerElement.querySelectorAll('.' + tabsClass);
for (let i = 0; i < tabbedContainers.length; i++) {
let tabbedContainer = tabbedContainers[i];
let tabList = tabbedContainer.querySelectorAll('.' + tabClass);
activateTab(tabList[0]);
for (let i = 0; i < tabList.length; i++) {
let tabElement = tabList[i];
let tabButton = tabElement.querySelector('.' + tabButtonClass);
tabButton.addEventListener('click', function (event) {
event.preventDefault();
if (this.classList.contains("disabled")) {
return;
}
activateTab(event.target.parentNode);
})
}
}
}
/**
* @desc A BIM viewer based on the [xeokit SDK](http://xeokit.io).
*
*
*/
class BIMViewer extends Controller {
/**
* Constructs a BIMViewer.
* @param {Server} server Data access strategy.
* @param {*} cfg Configuration.
*/
constructor(server, cfg = {}) {
if (!cfg.canvasElement) {
throw "Config expected: canvasElement";
}
if (!cfg.explorerElement) {
throw "Config expected: explorerElement";
}
if (!cfg.toolbarElement) {
throw "Config expected: toolbarElement";
}
if (!cfg.navCubeCanvasElement) {
throw "Config expected: navCubeCanvasElement";
}
const canvasElement = cfg.canvasElement;
const explorerElement = cfg.explorerElement;
const toolbarElement = cfg.toolbarElement;
const navCubeCanvasElement = cfg.navCubeCanvasElement;
const queryInfoPanelElement = cfg.queryInfoPanelElement;
const busyModelBackdropElement = cfg.busyModelBackdropElement;
explorerElement.oncontextmenu = (e) => {
e.preventDefault();
};
toolbarElement.oncontextmenu = (e) => {
e.preventDefault();
};
navCubeCanvasElement.oncontextmenu = (e) => {
e.preventDefault();
};
const viewer = new Viewer({
canvasElement: canvasElement,
transparent: true
});
super(null, cfg, server, viewer);
this._configs = {};
/**
* The xeokit [Viewer](https://xeokit.github.io/xeokit-sdk/docs/class/src/viewer/Viewer.js~Viewer.html) at the core of this BIMViewer.
*
* @type {Viewer}
*/
this.viewer = viewer;
this._customizeViewer();
this._initCanvasContextMenus();
this._initConfigs();
explorerElement.innerHTML = explorerTemplate;
toolbarElement.innerHTML = toolbarTemplate;
this._explorerElement = explorerElement;
initTabs(explorerElement);
this._modelsExplorer = new ModelsExplorer(this, {
modelsTabElement: explorerElement.querySelector(".xeokit-modelsTab"),
unloadModelsButtonElement: explorerElement.querySelector(".xeokit-unloadAllModels"),
modelsElement: explorerElement.querySelector(".xeokit-models")
});
this._objectsExplorer = new ObjectsExplorer(this, {
objectsTabElement: explorerElement.querySelector(".xeokit-objectsTab"),
showAllObjectsButtonElement: explorerElement.querySelector(".xeokit-showAllObjects"),
hideAllObjectsButtonElement: explorerElement.querySelector(".xeokit-hideAllObjects"),
objectsElement: explorerElement.querySelector(".xeokit-objects")
});
this._classesExplorer = new ClassesExplorer(this, {
classesTabElement: explorerElement.querySelector(".xeokit-classesTab"),
showAllClassesButtonElement: explorerElement.querySelector(".xeokit-showAllClasses"),
hideAllClassesButtonElement: explorerElement.querySelector(".xeokit-hideAllClasses"),
classesElement: explorerElement.querySelector(".xeokit-classes")
});
this._storeysExplorer = new StoreysExplorer(this, {
storeysTabElement: explorerElement.querySelector(".xeokit-storeysTab"),
showAllStoreysButtonElement: explorerElement.querySelector(".xeokit-showAllStoreys"),
hideAllStoreysButtonElement: explorerElement.querySelector(".xeokit-hideAllStoreys"),
storeysElement: explorerElement.querySelector(".xeokit-storeys")
});
this._resetAction = new ResetAction(this, {
buttonElement: toolbarElement.querySelector(".xeokit-reset"),
active: false
});
this._fitAction = new FitAction(this, {
buttonElement: toolbarElement.querySelector(".xeokit-fit"),
active: false
});
this._threeDMode = new ThreeDMode(this, {
buttonElement: toolbarElement.querySelector(".xeokit-threeD"),
active: false
});
this._firstPersonMode = new FirstPersonMode(this, {
buttonElement: toolbarElement.querySelector(".xeokit-firstPerson"),
active: false
});
this._hideTool = new HideTool(this, {
buttonElement: toolbarElement.querySelector(".xeokit-hide"),
active: false
});
this._selectionTool = new SelectionTool(this, {
buttonElement: toolbarElement.querySelector(".xeokit-select"),
active: false
});
this._queryTool = new QueryTool(this, {
buttonElement: toolbarElement.querySelector(".xeokit-query"),
queryInfoPanelElement: queryInfoPanelElement,
active: false
});
this._sectionTool = new SectionTool(this, {
buttonElement: toolbarElement.querySelector(".xeokit-section"),
active: false
});
this._navCubeMode = new NavCubeMode(this, {
navCubeCanvasElement: navCubeCanvasElement,
active: true
});
this._busyModal = new BusyModal(this, {
busyModalBackdropElement: busyModelBackdropElement
});
this._threeDMode.setActive(true);
this._firstPersonMode.setActive(false);
this._navCubeMode.setActive(true);
this._modelsExplorer.on("modelLoaded", (modelId) => {
if (this._modelsExplorer.getNumModelsLoaded() === 1) {
this.setControlsEnabled(true);
}
this.fire("modelLoaded", modelId);
});
this._modelsExplorer.on("modelUnloaded", (modelId) => {
if (this._modelsExplorer.getNumModelsLoaded() === 0) {
this.setControlsEnabled(false);
this.openTab("models");
}
this.fire("modelUnloaded", modelId);
});
this._queryTool.on("queryPicked", (event) => {
this.fire("queryPicked", event);
});
this._queryTool.on("queryNotPicked", () => {
this.fire("queryNotPicked", true);
});
this._resetAction.on("reset", () => {
this.fire("reset", true);
});
this._mutexActivation([this._queryTool, this._hideTool, this._selectionTool, this._sectionTool]);
explorerElement.querySelector(".xeokit-showAllObjects").addEventListener("click", (event) => {
this.showAllObjects();
this.xrayNoObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-hideAllObjects").addEventListener("click", (event) => {
this.hideAllObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-showAllClasses").addEventListener("click", (event) => {
this.showAllObjects();
this.xrayNoObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-hideAllClasses").addEventListener("click", (event) => {
this.hideAllObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-showAllStoreys").addEventListener("click", (event) => {
this.showAllObjects();
this.xrayNoObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-hideAllStoreys").addEventListener("click", (event) => {
this.hideAllObjects();
event.preventDefault();
});
explorerElement.querySelector(".xeokit-unloadAllModels").addEventListener("click", (event) => {
this.setControlsEnabled(false); // For quick UI feedback
this._modelsExplorer.unloadAllModels();
event.preventDefault();
});
this._bcfViewpointsPlugin = new BCFViewpointsPlugin(this.viewer, {});
}
_customizeViewer() {
const scene = this.viewer.scene;
scene.xrayMaterial.fill = false;
scene.xrayMaterial.fillAlpha = 0.1;
scene.xrayMaterial.fillColor = [0, 0, 0];
scene.xrayMaterial.edges = true;
scene.xrayMaterial.edgeAlpha = 0.3;
scene.xrayMaterial.edgeColor = [0, 0, 0];
scene.highlightMaterial.edges = true;
scene.highlightMaterial.edgeColor = [.5, .5, 0];
scene.highlightMaterial.edgeAlpha = 0.9;
scene.highlightMaterial.fill = true;
scene.highlightMaterial.fillAlpha = 0.1;
scene.highlightMaterial.fillColor = [1, 0, 0];
scene.clearLights();
new AmbientLight(scene, {
color: [0.3, 0.3, 0.3],
intensity: 1.0
});
new DirLight(scene, {
dir: [0.8, -0.6, -0.8],
color: [1.0, 1.0, 1.0],
intensity: 1.0,
space: "world"
});
new DirLight(scene, {
dir: [-0.8, -0.4, 0.4],
color: [1.0, 1.0, 1.0],
intensity: 1.0,
space: "world"
});
new DirLight(scene, {
dir: [0.2, -0.8, 0.8],
color: [0.6, 0.6, 0.6],
intensity: 1.0,
space: "world"
});
this.viewer.cameraControl.panRightClick = true;
this.viewer.cameraControl.panToPointer = true;
this.viewer.cameraControl.doublePickFlyTo = true;
// Scalable Ambient Obscurance (SAO) defaults
scene.camera.perspective.near = 0.05;
scene.camera.perspective.far = 3000.0;
scene.camera.ortho.near = 0.05;
scene.camera.ortho.far = 3000.0;
const sao = scene.sao;
sao.enabled = false;
sao.bias = 0.5;
sao.intensity = 0.5;
sao.scale = 1200.0;
sao.kernelRadius = 100;
// Only enable SAO and normal edge emphasis while camera is not moving
const timeoutDuration = 200;
var timer = timeoutDuration;
var saoEnabled = false;
const onCameraMatrix = scene.camera.on("matrix", () => {
if (this._configs.saoInteractive) {
return;
}
const saoInteractiveDelay = this._configs.saoInteractiveDelay;
timer = ((saoInteractiveDelay !== null && saoInteractiveDelay !== undefined) ? this._configs.saoInteractiveDelay : 200);
if (saoEnabled) {
scene.sao.enabled = false;
saoEnabled = false;
}
});
const onSceneTick = scene.on("tick", (e) => {
if (this._configs.saoInteractive) {
if (!saoEnabled) {
scene.sao.enabled = (!!this._configs.saoEnabled);
saoEnabled = true;
}
return;
}
if (saoEnabled) {
return;
}
timer -= e.deltaTime;
if (timer <= 0) {
if (!saoEnabled) {
scene.sao.enabled = (!!this._configs.saoEnabled);
saoEnabled = true;
}
}
});
}
_initCanvasContextMenus() {
this._canvasContextMenu = new CanvasContextMenu();
this._objectContextMenu = new ObjectContextMenu();
this.viewer.cameraControl.on("rightClick", (e) => {
const event = e.event;
const hit = this.viewer.scene.pick({
canvasPos: [event.offsetX, event.offsetY]
});
if (hit && hit.entity.isObject) {
this._canvasContextMenu.hide();
this._objectContextMenu.context = {
viewer: this.viewer,
bimViewer: this,
showObjectInExplorers: (objectId) => {
this.showObjectInExplorers(objectId);
const openTabId = this.getOpenTab();
if (openTabId !== "objects" && openTabId !== "classes" && openTabId !== "storeys") {
this.openTab("objects");
}
},
entity: hit.entity
};
this._objectContextMenu.show(event.pageX, event.pageY);
} else {
this._objectContextMenu.hide();
this._canvasContextMenu.context = {
viewer: this.viewer,
bimViewer: this
};
this._canvasContextMenu.show(event.pageX, event.pageY);
}
});
}
_initConfigs() {
this.setConfigs({
"cameraNear": "0.05",
"cameraFar": "3000.0",
"saoEnabled": "false",
"saoBias": "0.5",
"saoIntensity": "0.5",
"saoScale": "1200.0",
"saoKernelRadius": "100",
"xrayContext": true,
"backgroundColor": [1.0, 1.0, 1.0],
"saoInteractive": true,
"saoInteractiveDelay": 200,
"objectColorSource": "model"
});
}
/**
* Sets a batch of viewer configurations.
*
* @param {*} viewerConfigs Map of key-value configuration pairs.
*/
setConfigs(viewerConfigs) {
for (let name in viewerConfigs) {
if (viewerConfigs.hasOwnProperty(name)) {
const value = viewerConfigs[name];
this.setConfig(name, value);
}
}
}
/**
* Sets a viewer configuration.
*
* See class comments for the list of available viewer configurations.
*
* @param {String} name Configuration name.
* @param {*} value Configuration value.
*/
setConfig(name, value) {
function parseBool(value) {
return ((value === true) || (value === "true"));
}
try {
switch (name) {
case "backgroundColor":
const rgbColor = value;
this.setBackgroundColor(rgbColor);
this._configs[name] = rgbColor;
break;
case "cameraNear":
const near = parseFloat(value);
this.viewer.scene.camera.perspective.near = near;
this.viewer.scene.camera.ortho.near = near;
this._configs[name] = near;
break;
case "cameraFar":
const far = parseFloat(value);
this.viewer.scene.camera.perspective.far = far;
this.viewer.scene.camera.ortho.far = far;
this._configs[name] = far;
break;
case "saoEnabled":
this.viewer.scene.sao.enabled = this._configs[name] = parseBool(value);
break;
case "saoBias":
this.viewer.scene.sao.bias = parseFloat(value);
break;
case "saoIntensity":
this.viewer.scene.sao.intensity = parseFloat(value);
break;
case "saoScale":
this.viewer.scene.sao.scale = this._configs[name] = parseFloat(value);
break;
case "saoKernelRadius":
this.viewer.scene.sao.kernelRadius = this._configs[name] = parseFloat(value);
break;
case "saoBlur":
this.viewer.scene.sao.blur = this._configs[name] = parseBool(value);
break;
case "viewFitFOV":
this.viewer.cameraFlight.fitFOV = this._configs[name] = parseFloat(value);
break;
case "viewFitDuration":
this.viewer.cameraFlight.duration = this._configs[name] = parseFloat(value);
break;
case "perspectiveFOV":
this.viewer.camera.perspective.fov = this._configs[name] = parseFloat(value);
break;
case "excludeUnclassifiedObjects":
this._configs[name] = parseBool(value);
break;
case "objectColorSource":
this.setObjectColorSource(value);
this._configs[name] = value;
break;
case "xrayContext":
this._configs[name] = value;
break;
case "saoInteractive":
this._configs["saoInteractive"] = parseBool(value);
break;
case "saoInteractiveDelay":
var saoInteractiveDelay = parseFloat(value);
if (saoInteractiveDelay < 0) {
this.error("setConfig() - saoInteractiveDelay cannot be less than zero - clamping to zero");
saoInteractiveDelay = 0;
}
this._configs["saoInteractiveDelay"] = parseFloat(value);
break;
default:
this.error("setConfig() - unsupported configuration: '" + name + "'");
}
} catch (e) {
this.error("setConfig() - failed to configure '" + name + "': " + e);
}
}
/**
* Gets the value of a viewer configuration.
*
* These are set with {@link BIMViewer#setConfig} and {@link BIMViewer#setConfigs}.
*
* @param {String} name Configuration name.
* @ereturns {*} Configuration value.
*/
getConfig(name) {
return this._configs[name];
}
/**
* Gets information on all available projects.
*
* Internally, internally, the viewer obtains this information via via {@link Server#getProjects}.
*
* ### Example
*
* ````javascript
* myViewer.getProjectsInfo((projectsInfo) => {
* console.log(JSON.stringify(projectsInfo, null, "\t"));
* });
* ````
*
* Returns JSON similar to:
*
* ````json
* {
* "projects": [
* {
* "id": "Duplex",
* "name": "Duplex"
* },
* {
* "id": "Schependomlaan",
* "name": "Schependomlaan"
* },
* {
* "id": "WestRiversideHospital",
* "name": "West Riverside Hospital"
* }
* ]
* }
* ````
* @param {Function} done Callback invoked on success, into which the projects information JSON is passed.
* @param {Function} error Callback invoked on failure, into which the error message string is passed.
*/
getProjectsInfo(done, error) {
if (!done) {
this.error("getProjectsInfo() - Argument expected: 'done'");
return;
}
this.server.getProjects( done, (errorMsg) => {
this.error("getProjectsInfo() - " + errorMsg);
if (error) {
error(errorMsg);
}
});
}
/**
* Gets information on the given project.
*
* Internally, internally, the viewer obtains this information via via {@link Server#getProject}.
*
* Use {@link BIMViewer#getProjects} to get information on all available projects.
*
* ### Example
*
* ````javascript
* myViewer.getProjectInfo(("Duplex", (projectInfo) => {
* console.log(JSON.stringify(projectInfo, null, "\t"));
* });
* ````
*
* Returns JSON similar to:
*
* ````json
* {
* "id": "Duplex",
* "name": "Duplex"
* }
* ````
*
* @param {String} projectId ID of the project to get information on. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
* @param {Function} done Callback invoked on success, into which the project information JSON is passed.
* @param {Function} error Callback invoked on failure, into which the error message string is passed.
*/
getProjectInfo(projectId, done, error) {
if (!projectId) {
this.error("getProjectInfo() - Argument expected: projectId");
return;
}
if (!done) {
this.error("getProjectInfo() - Argument expected: 'done'");
return;
}
this.server.getProject(projectId,
done, (errorMsg) => {
this.error("getProjectInfo() - " + errorMsg);
if (error) {
error(errorMsg);
}
});
}
/**
* Gets information on the given object, belonging to the given model, within the given project.
*
* Internally, internally, the viewer obtains this information via via {@link Server#getObjectInfo}.
*
* ### Example
*
* ````javascript
* myViewer.getObjectProperties(("Duplex", "design", "0wkEuT1wr1kOyafLY4vy3H", (objectInfo) => {
* console.log(JSON.stringify(objectInfo, null, "\t"));
* });
* ````
*
* Returns JSON similar to:
*
* ````json
* {
* "projectId": "
* "name": "M_Tall Cabinet-Single Door(2):800 mm:157950",
* "type": "IfcFurniture",
* "uuid": "0wkEuT1wr1kOyafLY4vy3H",
* "parent": "0BTBFw6f90Nfh9rP1dlXrb",
* "id": "0wkEuT1wr1kOyafLY4vy3H"
* }
* ````
*
* @param {String} projectId ID of the project to get information on. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
* @param {String} modelId ID of a model within the project. Must be the ID of one of the models in the information obtained by {@link BIMViewer#getProjectInfo}.
* @param {String} objectId ID of an object in the model.
* @param {Function} done Callback invoked on success, into which the object information JSON is passed.
* @param {Function} error Callback invoked on failure, into which the error message string is passed.
*/
getObjectInfo(projectId, modelId, objectId, done, error) {
if (!projectId) {
this.error("getObjectInfo() - Argument expected: projectId");
return;
}
if (!modelId) {
this.error("getObjectInfo() - Argument expected: modelId");
return;
}
if (!objectId) {
this.error("getObjectInfo() - Argument expected: objectId");
return;
}
if (!done) {
this.error("getProjectInfo() - Argument expected: 'done'");
return;
}
this.server.getObjectInfo(projectId, modelId, objectId,
done,
(errorMsg) => {
if (error) {
error(errorMsg);
}
});
}
/**
* Loads a project into the viewer.
*
* Unloads any currently loaded project and its models first. If the given project is already loaded, will unload that project first.
*
* @param {String} projectId ID of the project to load. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
* @param {Function} done Callback invoked on success.
* @param {Function} error Callback invoked on failure, into which the error message string is passed.
*/
loadProject(projectId, done, error) {
if (!projectId) {
this.error("loadProject() - Argument expected: objectId");
return;
}
this._modelsExplorer.loadProject(projectId,
() => {
if (done) {
done();
}
}, (errorMsg) => {
this.error("loadProject() - " + errorMsg);
if (error) {
error(errorMsg);
}
});
}
/**
* Unloads whatever project is currently loaded.
*/
unloadProject() {
this._modelsExplorer.unloadProject();
this.openTab("models");
this.setControlsEnabled(false); // For quick UI feedback
}
/**
* Returns the ID of the currently loaded project, if any.
*
* @returns {String} The ID of the currently loaded project, otherwise ````null```` if no project is currently loaded.
*/
getLoadedProjectId() {
return this._modelsExplorer.getLoadedProjectId();
}
/**
* Returns the IDs of the models in the currently loaded project.
*
* @returns {String[]} The IDs of the models in the currently loaded project.
*/
getModelIds() {
return this._modelsExplorer.getModelIds();
}
/**
* Loads a model into the viewer.
*
* Assumes that the project containing the model is currently loaded.
*
* @param {String} modelId ID of the model to load. Must be the ID of one of the models in the currently loaded project.
* @param {Function} done Callback invoked on success.
* @param {Function} error Callback invoked on failure, into which the error message string is passed.
*/
loadModel(modelId, done, error) {
if (!modelId) {
this.error("loadModel() - Argument expected: modelId");
return;
}
this._modelsExplorer.loadModel(modelId,
() => {
if (done) {
done();
}
}, (errorMsg) => {
this.error("loadModel() - " + errorMsg);
if (error) {
error(errorMsg);
}
});
}
/**
* Load all models in the currently loaded project.
*
* Doesn't reload any models that are currently loaded.
*
* @param {Function} done Callback invoked on successful loading of the models.
*/
loadAllModels(done = function () {
}) {
const modelIds = this._modelsExplorer.getModelIds();
const loadNextModel = (i, done2) => {
if (i >= modelIds.length) {
done2();
} else {
const modelId = modelIds[i];
if (!this._modelsExplorer.isModelLoaded(modelId)) {
this._modelsExplorer.loadModel(modelId, () => {
loadNextModel(i + 1, done2);
}, (errorMsg) => {
this.error("loadAllModels() - " + errorMsg);
loadNextModel(i + 1, done2);
});
} else {
loadNextModel(i + 1, done2);
}
}
};
loadNextModel(0, done);
}
/**
* Returns the IDs of the currently loaded models, if any.
*
* @returns {String[]} The IDs of the currently loaded models, otherwise an empty array if no models are currently loaded.
*/
getLoadedModelIds() {
return this._modelsExplorer._getLoadedModelIds();
}
/**
* Gets if the given model is loaded.
*
* @param {String} modelId ID of the model to check. Must be the ID of one of the models in the currently loaded project.
* @returns {Boolean} True if the given model is loaded.
*/
isModelLoaded(modelId) {
if (!modelId) {
this.error("unloadModel() - Argument expected: modelId");
return;
}
return this._modelsExplorer.isModelLoaded(modelId);
}
/**
* Unloads a model from the viewer.
*
* Does nothing if the model is not currently loaded.
*
* @param {String} modelId ID of the model to unload.
*/
unloadModel(modelId) {
if (!modelId) {
this.error("unloadModel() - Argument expected: modelId");
return;
}
this._modelsExplorer.unloadModel(modelId);
}
/**
* Unloads all currently loaded models.
*/
unloadAllModels() {
this._modelsExplorer.unloadAllModels();
}
/**
* Sets the viewer's background color.
*
* @param {Number[]} rgbColor Three-element array of RGB values, each in range ````[0..1]````.
*/
setBackgroundColor(rgbColor) {
this.viewer.scene.canvas.canvas.style.background = "rgba(" + (rgbColor[0] * 255) + "," + (rgbColor[1] * 255) + "," + (rgbColor[2] * 255) + ", 1.0)";
}
/**
* Sets where the colors for model objects will be loaded from.
*
* Options are:
*
* * "model" - (default) load colors from models, and
* * "viewer" - load colors from the viewer's inbuilt table of colors for IFC types.
*
* This is "model" by default.
*
* @param {String} source Where colors will be loaded from - "model" or "viewer".
*/
setObjectColorSource(source) {
switch (source) {
case "model":
break;
case "viewer":
break;
default:
source = "model";
this.error("setObjectColorSource() - Unsupported value - accepted values are 'model' and 'viewer' - defaulting to 'model'");
return;
}
this._objectColorSource = source;
}
/**
* Gets where the colors for model objects will be loaded from.
*
* This is "model" by default.
*
* @return {String} Where colors will be loaded from - "model" to get colors from the model, or "viewer" to get them from the viewer's built-in table of colors for IFC types.
*/
getObjectColorSource() {
return this._objectColorSource || "model";
}
/**
* Highlights the given object in the tree views within the Objects, Classes and Storeys tabs.
*
* Also scrolls the object's node into view within each tree, then highlights it.
*
* De-highlights whatever node is currently highlighted in each of those trees.
*
* @param {String} objectId ID of the object
*/
showObjectInExplorers(objectId) {
if (!objectId) {
this.error("showObjectInExplorers() - Argument expected: objectId");
return;
}
this._objectsExplorer.showNodeInTreeView(objectId);
this._classesExplorer.showNodeInTreeView(objectId);
this._storeysExplorer.showNodeInTreeView(objectId);
}
/**
* De-highlights the object previously highlighted with {@link BIMViewer#showObjectInExplorers}.
*
* This only de-highlights the node. If the node is currently scrolled into view, then the node will remain in view.
*
* For each tab, does nothing if a node is currently highlighted.
*/
unShowObjectInExplorers() {
this._objectsExplorer.unShowNodeInTreeView();
this._classesExplorer.unShowNodeInTreeView();
this._storeysExplorer.unShowNodeInTreeView();
}
/**
* Shows the object with the given ID.
* @param {String} objectId ID of object to show.
*/
showObject(objectId) {
if (!objectId) {
this.error("showObject() - Argument expected: objectId");
return;
}
this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
const entity = this.viewer.scene.objects[metaObject.id];
if (entity) {
entity.visible = true;
}
});
}
/**
* Sets whether or not the given classes are visible.
*
* @param {String[]} classes Class types.
* @param {Boolean} visible Whether or not to show the classes.
*/
setClassesVisible(classes, visible) {
}
/**
* Sets whether or not the given models are visible.
*
* @param {String[]} modelIds ID of the models.
* @param {Boolean} visible Whether or not to show the models.
*/
setModelsVisible(modelIds, visible) {
if (!modelIds) {
this.error("setModelsVisible() - Argument expected: modelIds");
return;
}
if (visible === undefined || visible === null) {
this.error("setModelsVisible() - Argument expected: visible");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
for (var i = 0, len = modelIds.length; i < len; i++) {
const modelId = modelIds[i];
const model = scene.models[modelId];
if (!model) {
this.error("setModelsVisible() - Model not found in viewer: '" + modelId + "'");
continue;
}
model.visible = visible;
}
}
/**
* Shows all objects currently in the viewer.
*
* If any objects are currently X-rayed, they will remain X-rayed. Use {@link BIMViewer#xrayNoObjects} if you also need to undo X-ray on all objects.
*
* Likewise if any objects are currently selected, they will remain selected. Use {@link BIMViewer#deselectAllObjects} if you also need to undo selection on all objects.
*/
showAllObjects() {
this.viewer.scene.setObjectsVisible(this.viewer.scene.objectIds, true);
}
/**
* Shows all objects currently in the viewer, except for those with the given IDs.
* @param {String[]} objectIds IDs of objects to not show.
*/
showAllObjectsExceptFor(objectIds) {
if (!objectIds) {
this.error("showAllObjectsExceptFor() - Argument expected: objectId");
return;
}
}
/**
* Hides the object with the given ID.
* @param {String} objectId ID of object to hide.
*/
hideObject(objectId) { // TODO
if (!objectId) {
this.error("hideObject() - Argument expected: objectId");
return;
}
}
/**
* Hides all objects currently in the viewer.
*/
hideAllObjects() { // TODO
this.viewer.scene.setObjectsVisible(this.viewer.scene.visibleObjectIds, false);
}
/**
* Hides all objects currently in the viewer, except for those with the given IDs.
* @param {String[]} objectIds IDs of objects to not hide.
*/
hideAllObjectsExceptFor(objectIds) { // TODO
if (!objectIds) {
this.error("hideAllObjectsExceptFor() - Argument expected: objectId");
return;
}
}
/**
* Flies the camera to fit the given object in view.
*
* @param {String} objectId ID of the object
* @param {Function} done Callback invoked on completion
*/
flyToObject(objectId, done) {
if (!objectId) {
this.error("flyToObject() - Argument expected: objectId");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
const objectIds = [];
this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
if (scene.objects[metaObject.id]) {
objectIds.push(metaObject.id);
}
});
if (objectIds.length === 0) {
this.error("Object not found in viewer: '" + objectId + "'");
if (done) {
done();
}
return;
}
scene.setObjectsVisible(objectIds, true);
scene.setObjectsHighlighted(objectIds, true);
const aabb = scene.getAABB(objectIds);
viewer.cameraFlight.flyTo({
aabb: aabb
}, () => {
if (done) {
done();
}
setTimeout(function () {
scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
}, 500);
});
viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
}
/**
* Jumps the camera to fit the given object in view.
*
* @param {String} objectId ID of the object
*/
jumpToObject(objectId) {
if (!objectId) {
this.error("jumpToObject() - Argument expected: objectId");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
const objectIds = [];
this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
if (scene.objects[metaObject.id]) {
objectIds.push(metaObject.id);
}
});
if (objectIds.length === 0) {
this.error("Object not found in viewer: '" + objectId + "'");
return;
}
scene.setObjectsVisible(objectIds, true);
const aabb = scene.getAABB(objectIds);
viewer.cameraFlight.jumpTo({
aabb: aabb
});
viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
}
/**
* Fits the given models in view.
*
* @param {String[]} modelIds ID of the models.
* @param {Function} [done] Callback invoked on completion. Will be animated if this is given, otherwise will be instantaneous.
*/
viewFitModels(modelIds, done) {
if (!modelIds) {
this.error("viewFitModels() - Argument expected: modelIds");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
const aabb = math.AABB3();
math.collapseAABB3(aabb);
for (var i = 0, len = modelIds.length; i < len; i++) {
const modelId = modelIds[i];
const model = scene.models[modelId];
if (!model) {
this.error("Model not found in viewer: '" + modelId + "'");
continue;
}
model.visible = true;
model.highlighted = true;
math.expandAABB3(aabb, model.aabb);
}
if (done) {
viewer.cameraFlight.flyTo({
aabb: aabb
}, () => {
done();
setTimeout(function () {
scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
}, 500);
});
} else {
viewer.cameraFlight.jumpTo({
aabb: aabb
});
setTimeout(function () {
scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
}, 500);
}
viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
}
/**
* X-rays the object with the given ID.
*
* @param {String} objectId ID of object to x-ray.
*/
xrayObject(objectId) {
if (!objectId) {
this.error("xrayObject() - Argument expected: objectId");
return;
}
this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
const entity = this.viewer.scene.objects[metaObject.id];
if (entity) {
entity.xrayed = true;
}
});
}
/**
* X-rays all objects currently in the viewer.
*/
xrayAllObjects() {
this.viewer.scene.setObjectsXRayed(this.viewer.scene.objectIds, true);
}
/**
* X-rays all objects currently in the viewer, except for those with the given IDs.
* @param {String[]} objectIds IDs of objects to not x-ray.
*/
xrayAllObjectsExceptFor(objectIds) { // TODO
}
/**
* Sets whether or not the given models are X-rayed.
*
* @param {String[]} modelIds ID of the models.
* @param {Boolean} xrayed Whether or not to X-ray the models.
*/
setModelsXRayed(modelIds, xrayed) {
if (!modelIds) {
this.error("setModelsXRayed() - Argument expected: modelIds");
return;
}
if (xrayed === undefined || xrayed === null) {
this.error("setModelsXRayed() - Argument expected: xrayed");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
for (var i = 0, len = modelIds.length; i < len; i++) {
const modelId = modelIds[i];
const model = scene.models[modelId];
if (!model) {
this.error("setModelsXRayed() - Model not found in viewer: '" + modelId + "'");
continue;
}
model.xrayed = xrayed;
}
}
/**
* Un-x-rays all objects currently in the viewer.
*/
xrayNoObjects() {
this.viewer.scene.setObjectsXRayed(this.viewer.scene.objectIds, false);
}
/**
* Selects the objects with the given ID.
* @param {String} objectId ID of object to select.
*/
selectObject(objectId) {
if (!objectId) {
this.error("selectObject() - Argument expected: objectId");
return;
}
this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
const entity = this.viewer.scene.objects[metaObject.id];
if (entity) {
entity.selected = true;
}
});
}
/**
* Sets whether or not the given models are selected.
*
* @param {String[]} modelIds ID of the models.
* @param {Boolean} selected Whether or not to select the models.
*/
setModelsSelected(modelIds, selected) {
if (!modelIds) {
this.error("setModelsSelected() - Argument expected: modelIds");
return;
}
if (selected === undefined || selected === null) {
this.error("setModelsSelected() - Argument expected: selected");
return;
}
const viewer = this.viewer;
const scene = viewer.scene;
for (var i = 0, len = modelIds.length; i < len; i++) {
const modelId = modelIds[i];
const model = scene.models[modelId];
if (!model) {
this.error("setModelsSelected() - Model not found in viewer: '" + modelId + "'");
continue;
}
model.selected = selected;
}
}
/**
* Selects all objects currently in the viewer.
*/
selectAllObjects() {
this.viewer.scene.setObjectsSelected(this.viewer.scene.objectIds, true);
}
/**
* Selects all objects currently in the viewer, except for those with the given IDs.
*
* This causes the objects to glow with the selection color.
*
* @param {String[]} objectIds IDs of objects to not select.
*/
selectAllObjectsExceptFor(objectIds) { // TODO
}
/**
* De-selects all objects currently in the viewer.
*
* This removes the selection color from the objects.
*/
deselectAllObjects() {
this.viewer.scene.setObjectsSelected(this.viewer.scene.selectedObjectIds, false);
}
/**
* Opens the specified viewer tab.
*
* The available tabs are:
*
* * "models" - the Models tab, which lists the models available within the currently loaded project,
* * "objects" - the Objects tab, which contains a tree view for each loaded model, organized to indicate the containment hierarchy of their objects,
* * "classes" - the Classes tab, which contains a tree view for each loaded model, with nodes grouped by IFC types of their object