/**
A **Download** asynchronously downloads {{#crossLink "Objects"}}{{/crossLink}} from a BIMSurfer into the
parent {{#crossLink "Viewer"}}{{/crossLink}}.
## Overview
The **downloadType** config specifies the type of download:
<ul>
<li>"types" - download {{#crossLink "Object"}}Objects{{/crossLink}} of the given IFC type</li>
<li>"revision" - download {{#crossLink "Object"}}Objects{{/crossLink}} belonging to the given revision</li>
<li>"oids" - download {{#crossLink "Object"}}Objects{{/crossLink}} having the given IDs</li>
</ul>
## Example 1: General usage
````Javascript
// Create a Viewer
var viewer = new BIMSURFER.Viewer({
element: "myDiv"
});
// Initiate a Download
var download = new BIMSURFER.Download(viewer, {
downloadType: "types",
roid: "xyz",
types: ["", "", ""],
schema: "",
autoDestroy: true // default
});
// Subscribe to progress updates
download.on("progress", function(e) {
// Total number of Objects being loaded
var numObjects = e.numObjects;
// Number of Objects loaded so far
var numObjectsLoaded = e.numObjectsLoaded;
// Percentage of Objects loaded so far
var progress = e.progress;
//...
});
// Subscribe to completion
download.on("completed", function(e) {
// Number of Objects loaded
var numObjectsLoaded = e.numObjectsLoaded;
// Since this Download component was configured with autoDestroy: true,
// which is the default, then this Download component will now
// destroy itself.
//...
});
// Subscribe to errors
download.on("error", function(e) {
// Error message
var message = e;
// Even though this Download component was configured with autoDestroy: true,
// which is the default, the Download component will not destroy itself
// since an error occurred.
//...
});
````
## Example 2: Downloading {{#crossLink "Object"}}Objects{{/crossLink}} of specified IFC type
```` javascript
var downloadTypes = new BIMSURFER.Download(viewer, {
downloadType: "types",
types: [
"IfcDoor",
"IfcBuildingElementProxy",
"IfcWallStandardCase",
"IfcWall"
],
schema: "XYZ"
});
//...
````
## Example 3: Downloading {{#crossLink "Object"}}Objects{{/crossLink}} for a revision ID
```` javascript
var downloadRevisions = new BIMSURFER.Download(viewer, {
downloadType: "revision",
roid: "XYZ"
});
//...
````
## Example 4: Downloading {{#crossLink "Object"}}Objects{{/crossLink}} having the given IDs
```` javascript
var downloadByIDs = new BIMSURFER.Download(viewer, {
downloadType: "oids",
roids: ["XYZ", "XYZ2"],
oids: ["XYZ", "XYZ2"]
});
//...
````
@class Download
@module BIMSURFER
@submodule loading
@constructor
@param [viewer] {Viewer} Parent {{#crossLink "Viewer"}}{{/crossLink}}.
@param [cfg] {*} Configs
@param [cfg.id] {String} Optional ID, unique among all components in the parent viewer, generated automatically when omitted.
@param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Label.
@param [cfg.models] {*}
@param [cfg.downloadType] {*} Type download - "types", "revision" or "oids"
@param [cfg.roids] {*} A list of revision IDs
@param [cfg.roid] {*} A single revision ID
@param [cfg.schema] {*} Schema
@param [cfg.types] {*} Types of objects to download
@param [cfg.oids] {*} IDs of objects to download
@param [cfg.autoDestroy=true] {Boolean} Indicates if this Download component should destroy itself when download complete.
@extends Component
*/
(function () {
"use strict";
BIMSURFER.Download = BIMSURFER.Component.extend({
_init: function (cfg) {
this._api = cfg.api;
// Download parameters
this._models = cfg.models;
this._downloadType = cfg.downloadType;
this._roids = cfg.roids;
this._roid = cfg.roid;
this._schema = cfg.schema;
this._types = cfg.types;
this._oids = cfg.oids;
this._autoDestroy = cfg.autoDestroy;
// API handle
this._topicId = null;
// Download progress
this._downloading = false;
this._numObjects = 0;
this._numObjectsLoaded = 0;
// Queus of incoming data packets
this._dataPackets = [];
var self = this;
// Find serializer in API
this._api.getMessagingSerializerByPluginClassName(
"org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingSerializerPlugin",
function (serializer) {
// Build and send the download command to the API
var proc;
var params;
if (self._downloadType === "types") {
proc = "downloadByTypes";
params = {
roids: [self._roid],
schema: self._schema,
classNames: self._types,
serializerOid: serializer.oid,
includeAllSubtypes: false,
useObjectIDM: false,
sync: false,
deep: false
};
} else if (self._downloadType === "revision") {
proc = "download";
params = {
roid: self._roid,
serializerOid: serializer.oid,
sync: false,
showOwn: true
};
} else if (self._downloadType === "oids") {
proc = "downloadByOids";
params = {
roids: self._roids,
oids: self._oids,
serializerOid: serializer.oid,
sync: false,
deep: false
};
} else {
self.error("Unsupported downloadType: " + self._downloadType);
return;
}
// Send the download request
self._api.call("Bimsie1ServiceInterface", proc, params,
function (topicId) {
self._topicId = topicId;
self._api.registerProgressHandler(
self._topicId,
function (topicId, state) {
self._progressHandler(topicId, state);
},
function (topicId, state) {
//self._afterRegistration(topicId, state);
});
},
function (err) {
var message = "Download failed: " + err.__type + ": " + err.message;
self.error(message);
self.fire("error", message);
});
});
// Start processing response data packet queue
this._tick = this.viewer.on("tick",
function () {
var data = self._dataPackets.shift();
while (data != null) {
var inputStream = new BIMSURFER.DataInputStreamReader(null, data);
var channel = inputStream.readInt();
var numMessages = inputStream.readInt();
for (var i = 0; i < numMessages; i++) {
var messageType = inputStream.readByte();
if (messageType === 0) {
self._readStart(inputStream);
} else {
self._readObject(inputStream, messageType);
}
}
data = self._dataPackets.shift();
}
});
},
_progressHandler: function (topicId, state) {
if (topicId === this._topicId) {
if (state.title == "Done preparing") {
if (!this._downloading) {
this._downloading = true;
this._startDownload();
}
}
if (state.state == "completed") {
this._api.unregisterProgressHandler(this._topicId, this._progressHandler);
/**
* Fired when this Download has successfully completed.
*
* @event finished
*/
this.fire("completed", true);
}
}
},
_startDownload: function () {
this._numObjectsLoaded = 0;
this._numObjects = 0;
//this.viewer.SYSTEM.events.trigger('progressStarted', ['Loading Geometry']);
//this.viewer.SYSTEM.events.trigger('progressBarStyleChanged', BIMSURFER.Constants.ProgressBarStyle.Continuous);
// Bind callback to get data
var self = this;
this._api.setBinaryDataListener(this._topicId,
function (data) {
self._dataPackets.push(data);
});
// Request the data via Web Socket
this._api.downloadViaWebsocket({
longActionId: this._topicId,
topicId: this._topicId
});
},
_readStart: function (data) {
var start = data.readUTF8();
if (start != "BGS") {
this.error("Stream does not start with BGS (" + start + ")");
return false;
}
var version = data.readByte();
if (version != 4 && version != 5 && version != 6) {
this.error("Unimplemented version");
return false;
} else {
this._version = version;
}
data.align4();
var modelBounds = data.readFloatArray(6);
modelBounds = {
min: {x: modelBounds[0], y: modelBounds[1], z: modelBounds[2]},
max: {x: modelBounds[3], y: modelBounds[4], z: modelBounds[5]}
};
// Bump Viewer origin to center the model
this.viewer.origin = [
-(modelBounds.max.x + modelBounds.min.x) / 2,
-(modelBounds.max.y + modelBounds.min.y) / 2,
-(modelBounds.max.z + modelBounds.min.z) / 2
];
var firstModel = true;
if (firstModel) {
// Set up the Viewer's default Camera
var camera = this.viewer.camera;
camera.active = true; // Deactivates other Cameras
camera.eye = [
(modelBounds.max.x - modelBounds.min.x) * 0.5,
(modelBounds.max.y - modelBounds.min.y) * -1,
(modelBounds.max.z - modelBounds.min.z) * 0.5
];
var diagonal = Math.sqrt(
Math.pow(modelBounds.max.x - modelBounds.min.x, 2) +
Math.pow(modelBounds.max.y - modelBounds.min.y, 2) +
Math.pow(modelBounds.max.z - modelBounds.min.z, 2));
var far = diagonal * 5; // 5 being a guessed constant that should somehow coincide with the max zoom-out-factor
camera.far = far;
camera.near = far / 1000;
camera.fovy = 37.8493;
}
this._numObjects = data.readInt();
this._notifyProgress();
},
_notifyProgress: function () {
if (this._numObjectsLoaded < this._numObjects) {
// Download still in progress
var progress = Math.ceil(100 * this._numObjectsLoaded / this._numObjects);
if (progress != this._lastProgress) {
/**
* Fired periodically as downloading progresses, to indicate download progress.
* @event progress
* @param progress Progress percentage
* @param numObjects Total number of objects to download
* @param numObjectsLoaded Number of objects downloaded so far
*/
this.fire("progress", {
progress: progress,
numObjectsLoaded: this._numObjectsLoaded,
numObjects: this._numObjects
});
this._lastProgress = progress;
}
} else {
// Download completed
this.fire("progress", {
progress: 100,
numObjectsLoaded: this._numObjectsLoaded,
numObjects: this._numObjects
});
/**
* Fired when download has completed
* @event finished
*/
this.fire("completed", {
numObjects: this._numObjects
});
this._api.call("ServiceInterface", "cleanupLongAction", {
actionId: this._topicId
},
function () {
});
if (this._autoDestroy) {
this.destroy();
}
}
},
/**
* Reads an object from binary data packet.
*
* @param data The binary data packet.
* @param geometryType Type of geometry to read.
* @private
*/
_readObject: function (data, geometryType) {
var type = data.readUTF8();
var roid = data.readLong();
var objectId = data.readLong();
var geometryId;
var geometryIds = [];
var numGeometries;
var numParts;
var objectBounds;
var numIndices;
var indices;
var numPositions;
var positions;
var numNormals;
var normals;
var numColors;
var colors;
data.align4();
var matrix = data.readFloatArray(16);
if (geometryType == 1) {
objectBounds = data.readFloatArray(6);
geometryId = data.readLong();
numIndices = data.readInt();
indices = data.readIntArray(numIndices);
numPositions = data.readInt();
positions = data.readFloatArray(numPositions);
numNormals = data.readInt();
normals = data.readFloatArray(numNormals);
numColors = data.readInt();
colors = data.readFloatArray(numColors);
this._createGeometry(geometryId, positions, normals, colors, indices);
this._createObject(roid, objectId, objectId, [geometryId], type, matrix);
} else if (geometryType == 2) {
geometryId = data.readLong();
this._createObject(roid, objectId, objectId, [geometryId], type, matrix);
} else if (geometryType == 3) {
numParts = data.readInt();
for (var i = 0; i < numParts; i++) {
// Object contains multiple geometries
geometryId = data.readLong();
numIndices = data.readInt();
indices = data.readIntArray(numIndices);
numPositions = data.readInt();
positions = data.readFloatArray(numPositions);
numNormals = data.readInt();
normals = data.readFloatArray(numNormals);
numColors = data.readInt();
colors = data.readFloatArray(numColors);
this._createGeometry(geometryId, positions, normals, colors, indices);
geometryIds.push(geometryId);
}
this._createObject(roid, objectId, objectId, geometryIds, type, matrix);
} else if (geometryType == 4) {
// Object contains multiple instances of geometries
numGeometries = data.readInt();
geometryIds = [];
for (var i = 0; i < numGeometries; i++) {
geometryIds.push(data.readLong());
}
this._createObject(roid, objectId, objectId, geometryIds, type, matrix);
} else {
//this.warn("Unsupported geometry type: " + geometryType);
return;
}
},
_createGeometry: function (geometryId, positions, normals, colors, indices) {
new BIMSURFER.Geometry(this.viewer, {
id: geometryId,
positions: positions,
normals: normals,
colors: colors,
indices: indices,
primitive: "triangles"
});
},
_createObject: function (roid, oid, objectId, geometryIds, type, matrix) {
var self = this;
if (this.viewer.components[objectId]) {
this.error("Component with this ID already exists: " + objectId);
return;
}
this._models[roid].get(oid,
function (object) {
new BIMSURFER.Object(self.viewer, {
id: objectId,
type: type,
geometries: geometryIds,
matrix: matrix,
active: object.trans.mode === 0
});
this._numObjectsLoaded++;
this._notifyProgress();
});
},
_destroy: function () {
if (this._tick) {
this.viewer.off(this._tick);
}
}
});
})();