UNPKG

xeokit-viewer

Version:

BIM viewer built on xeokit and Bootstrap

416 lines (359 loc) 18 kB
import {Plugin} from "../../viewer/Plugin.js"; import {PerformanceModel} from "../../viewer/scene/PerformanceModel/PerformanceModel.js"; import {BIMServerPerformanceGeometryLoader} from "./lib/BIMServerPerformanceGeometryLoader.js"; import {loadBIMServerMetaModel} from "./lib/loadBIMServerMetaModel.js"; import {IFCObjectDefaults} from "../../viewer/metadata/IFCObjectDefaults.js"; import {utils} from "../../viewer/scene/utils.js"; /** * {@link Viewer} plugin that loads models from a [BIMServer](http://bimserver.org). * * Tested with BIMServer v1.5.120 and IFC schema ifc2x3tc1. * * * For each model loaded, creates a tree of {@link Entity}s within its {@link Viewer}'s {@link Scene}, with the root {@link Entity} representing the model and sub-{@link Entity}s representing objects within the model. * * The root {@link Entity} will have {@link Entity#isModel} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#models}. * * Each sub-{@link Entity} that represents an object will have {@link Entity#isObject} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#objects}. * * When loading a model, we can specify a World-space transformation to position and orient it, along with initial rendering states to recursively apply to the Entity tree. * * ## Usage * * In the example below, we'll load the latest revision of a project's model from BIMSserver. We'll assume that we have a BIMServer * instance running and serving requests on port 8082, with a model loaded for project ID ````131073````. * * Since xeokit's default World "up" direction is +Y, while the model's "up" is +Z, we'll rotate the * model 90 degrees about the X-axis as we load it. Note that we could also instead configure xeokit to use +Z as "up". * * A BIMServerLoaderPlugin is configured with a BIMServerClient, which provides a client-side facade through which * we can issue RPC calls to the BIMServer Service Interface. The BIMServerLoaderPlugin makes RPC calls through that * to download the model and it's IFC metadata. * * Note that BIMServerLoaderPlugin works with BIMServer V1.5.120 or later. * * Read more about this example in the user guide on [Viewing Models from BIMServer](https://github.com/xeokit/xeokit-sdk/wiki/Viewing-Models-from-BIMServer). * * [[Run this example](http://xeokit.github.io/xeokit-sdk/examples/#BIMServer_Schependomlaan)] * * ````javascript * import {Viewer} from "./../src/viewer/Viewer.js"; * import BimServerClient from "./../src/viewer/utils/BIMServerClient/bimserverclient.js" * import {BIMServerLoaderPlugin} from "./../src/plugins/BIMServerLoaderPlugin/BIMServerLoaderPlugin.js"; * * const bimServerAddress = "https://xeokit.cleverapps.io/"; * const username = "kevin.flynn@en.com"; * const password = "secretPassword"; * const poid = 196609; * * // Create a xeokit Viewer * const viewer = new Viewer({ * canvasId: "myCanvas" * }); * * // Create a BimServerClient * const bimServerClient = new BimServerClient(bimServerAddress); * * // Add a BIMServerLoaderPlugin to the Viewer, configured with the BIMServerClient * const bimServerLoader = new BIMServerLoaderPlugin(viewer, { * bimServerClient: bimServerClient * }); * * // Initialize the BIMServer client * bimServerClient.init(() => { * * // Login to BIMServer * bimServerClient.login(username, password, () => { * * // Query a project by ID * bimServerClient.call("ServiceInterface", "getProjectByPoid", { * poid: poid * }, (project) => { * * // From the project info returned by BIMServerClient, we'll get the ID of the latest * // model revision and the version of the IFC schema to which the model conforms. * * // Load the latest revision of the project * * const roid = project.lastRevisionId; * const schema = project.schema; * * var model = bimServerLoader.load({ // Returns an Entity * id: "myModel", * poid: poid, // Project ID * roid: roid, // Revision ID * schema: schema, // Schema version * edges: true, // Render with emphasized edges (default is false) * lambertMaterial: true, // Lambertian flat-shading instead of default Blinn/Phong * scale: [0.001, 0.001, 0.001], // Shrink the model a bit * rotation: [-90, 0, 0] // Rotate model for World +Y "up" * }); * * const scene = viewer.scene; // xeokit.Scene * const camera = scene.camera; // xeokit.Camera * * model.on("loaded", () => { // When loaded, fit camera and start orbiting * camera.orbitPitch(20); * viewer.cameraFlight.flyTo(model); * scene.on("tick", () => { * camera.orbitYaw(0.3); * }) * * // We can find the model Entity by ID * model = viewer.scene.models["myModel"]; * * // To destroy the model, call destroy() on the model Entity * model.destroy(); * }); * * model.on("error", function(errMsg} * console.error("Error while loading: " + errMsg); * }); * }); * }); * }); * ```` * @class BIMServerLoaderPluginOLD */ class BIMServerLoaderPlugin extends Plugin { /** * @constructor * @param {Viewer} viewer The Viewer. * @param {Object} cfg Plugin configuration. * @param {String} [cfg.id="BIMServerModels"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}. * @param {Object} cfg.bimServerClient A BIMServer client API instance. * @param {{String:Object}} [cfg.objectDefaults] Map of initial default states for each loaded {@link Entity} that represents an object. Default value for this parameter is {@link IFCObjectDefaults}. */ constructor(viewer, cfg) { super("BIMServerModels", viewer, cfg); /** * Version of BIMServer supported by this plugin. * @type {string} */ this.BIMSERVER_VERSION = "1.5"; if (!cfg.bimServerClient) { this.error("Config expected: bimServerClient"); } /** * The BIMServer API. * @type {BIMServerClient} */ this.bimServerClient = cfg.bimServerClient; /** * IFC types that are hidden by default. * @type {{IfcOpeningElement: boolean, IfcSpace: boolean}} */ this.hiddenTypes = { "IfcOpeningElement": true, "IfcSpace": true }; this.objectDefaults = cfg.objectDefaults; } /** * Sets map of initial default states for each loaded {@link Entity} that represents an object. * * Default value is {@link IFCObjectDefaults}. * * @type {{String: Object}} */ set objectDefaults(value) { this._objectDefaults = value || IFCObjectDefaults; } /** * Gets map of initial default states for each loaded {@link Entity} that represents an object. * * Default value is {@link IFCObjectDefaults}. * * @type {{String: Object}} */ get objectDefaults() { return this._objectDefaults; } /** * Loads a model from a BIMServer into this BIMServerLoaderPlugin's {@link Viewer}. * * Creates a tree of {@link Entity}s that represents the model. * * The root {@link Entity} will have {@link Entity#isModel} set true to indicate that it represents a model, and will therefore be registered in {@link Scene#models}. * * @param {Object} params Loading parameters. * @param {String} [params.id] ID to assign to the root {@link Entity#id}, unique among all components in the Viewer's {@link Scene}, generated automatically by default. * @param {Number} params.poid ID of the model's project within BIMServer. * @param {Number} params.roid ID of the model's revision within BIMServer. See the class example for how to query the latest project revision ID via the BIMServer client API. * @param {Number} params.schema The model's IFC schema. See the class example for how to query the project's schema via the BIMServer client API. * @param {{String:Object}} [params.objectDefaults] Map of initial default states for each loaded {@link Entity} that represents an object. Default value for this parameter is {@link IFCObjectDefaults}. * @param {Boolean} [params.edges=false] Whether or not xeokit renders the model with edges emphasized. * @param {Number[]} [params.position=[0,0,0]] The model World-space 3D position. * @param {Number[]} [params.scale=[1,1,1]] The model's World-space scale. * @param {Number[]} [params.rotation=[0,0,0]] The model's World-space rotation, as Euler angles given in degrees, for each of the X, Y and Z axis. * @param {Number[]} [params.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] The model's world transform matrix. Overrides the position, scale and rotation parameters. * @param {Boolean} [params.backfaces=false] When true, allows visible backfaces, wherever specified. When false, ignores backfaces. * @param {Number} [params.edgeThreshold=20] When xraying, highlighting, selecting or edging, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn. * @returns {Entity} Root Entity representing the loaded model. The Entity will have {@link Entity#isModel} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#models} */ load(params) { const self = this; params = params || {}; if (params.id && this.viewer.scene.components[params.id]) { this.error("Component with this ID already exists in viewer: " + params.id + " - will autogenerate this ID"); delete params.id; } const poid = params.poid; const roid = params.roid; const schema = params.schema; const viewer = this.viewer; const scene = viewer.scene; const bimServerClient = this.bimServerClient; const objectDefaults = params.objectDefaults || this._objectDefaults || IFCObjectDefaults; const performanceModel = new PerformanceModel(scene, params); const modelId = performanceModel.id; var onTick; if (!poid) { this.error("load() param expected: poid"); return performanceModel; // TODO: Finalize? } if (!roid) { this.error("load() param expected: roid"); return performanceModel; // TODO: Finalize? } if (!schema) { this.error("load() param expected: schema"); return performanceModel; // TODO: Finalize? } const logging = !!params.logging; scene.canvas.spinner.processes++; bimServerClient.getModel(poid, roid, schema, false, bimServerClientModel => { // TODO: Preload not necessary combined with the bruteforce tree loadBIMServerMetaModel(viewer, modelId, poid, roid, schema, bimServerClientModel).then(function () { performanceModel.once("destroyed", function () { viewer.metaScene.destroyMetaModel(modelId); }); const oids = []; const oidToGuid = {}; const guidToOid = {}; const visit = metaObject => { oids[metaObject.external.gid] = metaObject.external.extId; oidToGuid[metaObject.external.extId] = metaObject.id; guidToOid[metaObject.id] = metaObject.external.extId; for (let i = 0; i < (metaObject.children || []).length; ++i) { visit(metaObject.children[i]); } }; const metaModel = viewer.metaScene.metaModels[modelId]; const rootMetaObject = metaModel.rootMetaObject; visit(rootMetaObject); const loader = new BIMServerPerformanceGeometryLoader(bimServerClient, bimServerClientModel, roid, null, { objectDefaults: objectDefaults, log: function (msg) { if (logging) { self.log(msg); } }, error: function (msg) { self.error(msg); performanceModel.fire("error", msg); }, warn: function (msg) { self.warn(msg); }, gotModelBoundary: function (boundary) { const xmin = boundary[0]; const ymin = boundary[1]; const zmin = boundary[2]; const xmax = boundary[3]; const ymax = boundary[4]; const zmax = boundary[5]; const diagonal = Math.sqrt( Math.pow(xmax - xmin, 2) + Math.pow(ymax - ymin, 2) + Math.pow(zmax - zmin, 2)); const scale = 100 / diagonal; const center = [ scale * ((xmax + xmin) / 2), scale * ((ymax + ymin) / 2), scale * ((zmax + zmin) / 2) ]; // TODO //o.viewer.setScale(scale); // Temporary until we find a better scaling system. }, createGeometry: function (geometryDataId, positions, normals, indices) { const geometryId = `${modelId}.${geometryDataId}`; performanceModel.createGeometry({ id: geometryId, primitive: "triangles", positions: positions, normals: normals, indices: indices }); }, createMeshInstancingGeometry: function (geometryDataId, matrix, color, opacity) { const meshId = `${modelId}.${geometryDataId}.mesh`; const geometryId = `${modelId}.${geometryDataId}`; performanceModel.createMesh({ id: meshId, geometryId: geometryId, matrix: matrix, color: color, opacity: opacity }); }, createMeshSpecifyingGeometry: function (geometryDataId, positions, normals, indices, matrix, color, opacity) { const meshId = `${modelId}.${geometryDataId}.mesh`; performanceModel.createMesh({ id: meshId, primitive: "triangles", positions: positions, normals: normals, indices: indices, matrix: matrix, color: color, opacity: opacity }); }, createEntity(id, geometryDataId, ifcType) { // Pass in color to set transparency id = oidToGuid[id]; const meshId = `${modelId}.${geometryDataId}.mesh`; if (scene.objects[id]) { self.error(`Can't create object - object with id ${id} already exists`); return; } if (scene.components[id]) { self.error(`Can't create object - scene component with this ID already exists: ${id}`); return; } ifcType = ifcType || "DEFAULT"; const props = objectDefaults[ifcType] || {}; performanceModel.createEntity(utils.apply(props, { id: id, isObject: true, meshIds: [meshId] })); } }); loader.addProgressListener((progress, nrObjectsRead, totalNrObjects) => { if (progress === "start") { if (logging) { self.log("Started loading geometries"); } } else if (progress === "done") { if (logging) { self.log(`Finished loading geometries (${totalNrObjects} objects received)`); } viewer.scene.off(onTick); scene.canvas.spinner.processes--; performanceModel.finalize(); performanceModel.fire("loaded"); } }); loader.setLoadOids(oids); // TODO: Why do we do this? onTick = viewer.scene.on("tick", () => { loader.process(); }); loader.start(); }); }); return performanceModel; } /** * Destroys this BIMServerLoaderPlugin. */ destroy() { super.destroy(); } } export {BIMServerLoaderPlugin}