src/plugins/BIMServerLoaderPlugin/BIMServerClient/model.js
import BimServerApiPromise from "./bimserverapipromise.js";
/**
* @private
*/
export default class Model {
constructor(bimServerApi, poid, roid, schema) {
this.schema = schema;
this.bimServerApi = bimServerApi;
this.poid = poid;
this.roid = roid;
this.waiters = [];
this.objects = {};
this.objectsByGuid = {};
this.objectsByName = {};
this.oidsFetching = {};
this.guidsFetching = {};
this.namesFetching = {};
// Those are only fully loaded types (all of them), should not be stored here if loaded partially
this.loadedTypes = [];
this.loadedDeep = false;
this.changedObjectOids = {};
this.loading = false;
this.logging = true;
this.changes = 0;
this.changeListeners = [];
}
init(callback) {
callback();
}
load(deep, modelLoadCallback) {
const othis = this;
if (deep) {
this.loading = true;
this.bimServerApi.getJsonStreamingSerializer(function (serializer) {
othis.bimServerApi.call("ServiceInterface", "download", {
roids: [othis.roid],
serializerOid: serializer.oid,
sync: false
}, function (topicId) {
const url = othis.bimServerApi.generateRevisionDownloadUrl({
topicId: topicId,
serializerOid: serializer.oid
});
othis.bimServerApi.getJson(url, null, function (data) {
data.objects.forEach(function (object) {
othis.objects[object._i] = othis.createWrapper(object, object._t);
});
othis.loading = false;
othis.loadedDeep = true;
othis.waiters.forEach(function (waiter) {
waiter();
});
othis.waiters = [];
othis.bimServerApi.call("ServiceInterface", "cleanupLongAction", {
topicId: topicId
}, function () {
if (modelLoadCallback != null) {
modelLoadCallback(othis);
}
});
}, function (error) {
console.log(error);
});
});
});
} else {
if (modelLoadCallback != null) {
modelLoadCallback(othis);
}
}
}
// Start a transaction, make sure to wait for the callback to be called, only after that the transaction will be active
startTransaction(callback) {
this.bimServerApi.call("LowLevelInterface", "startTransaction", {
poid: this.poid
}, (tid) => {
this.tid = tid;
callback(tid);
});
}
// Checks whether a transaction is running, if not, it throws an exception, otherwise it return the tid
checkTransaction() {
if (this.tid != null) {
return this.tid;
}
throw new Error("No transaction is running, call startTransaction first");
}
create(className, object, callback) {
const tid = this.checkTransaction();
object._t = className;
const wrapper = this.createWrapper({}, className);
this.bimServerApi.call("LowLevelInterface", "createObject", {
tid: tid,
className: className
}, (oid) => {
wrapper._i = oid;
this.objects[object._i] = wrapper;
object._s = 1;
if (callback != null) {
callback(object);
}
});
return object;
}
reset() {
}
commit(comment, callback) {
const tid = this.checkTransaction();
this.bimServerApi.call("LowLevelInterface", "commitTransaction", {
tid: tid,
comment: comment
}, function (roid) {
if (callback != null) {
callback(roid);
}
});
}
abort(callback) {
const tid = this.checkTransaction();
this.bimServerApi.call("LowLevelInterface", "abortTransaction", {
tid: tid
}, function () {
if (callback != null) {
callback();
}
});
}
addChangeListener(changeListener) {
this.changeListeners.push(changeListener);
}
incrementChanges() {
this.changes++;
this.changeListeners.forEach((changeListener) => {
changeListener(this.changes);
});
}
extendClass(wrapperClass, typeName) {
let realType = this.bimServerApi.schemas[this.schema][typeName];
if (typeName === "GeometryInfo" || typeName === "GeometryData") {
realType = this.bimServerApi.schemas.geometry[typeName];
}
realType.superclasses.forEach((typeName) => {
this.extendClass(wrapperClass, typeName);
});
const othis = this;
for (let fieldName in realType.fields) {
const field = realType.fields[fieldName];
field.name = fieldName;
wrapperClass.fields.push(field);
(function (field, fieldName) {
if (field.reference) {
wrapperClass["set" + fieldName.firstUpper() + "Wrapped"] = function (typeName, value) {
const object = this.object;
object[fieldName] = {
_t: typeName,
value: value
};
const tid = othis.checkTransaction();
const type = othis.bimServerApi.schemas[othis.schema][typeName];
const wrappedValueType = type.fields.wrappedValue;
if (wrappedValueType.type === "string") {
othis.bimServerApi.call("LowLevelInterface", "setWrappedStringAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
type: typeName,
value: value
}, function () {
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
othis.incrementChanges();
});
}
};
wrapperClass["set" + fieldName.firstUpper()] = function (value) {
const tid = othis.checkTransaction();
const object = this.object;
object[fieldName] = value;
if (value == null) {
othis.bimServerApi.call("LowLevelInterface", "unsetReference", {
tid: tid,
oid: object._i,
referenceName: fieldName,
}, function () {
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
});
} else {
othis.bimServerApi.call("LowLevelInterface", "setReference", {
tid: tid,
oid: object._i,
referenceName: fieldName,
referenceOid: value._i
}, function () {
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
});
}
};
wrapperClass["add" + fieldName.firstUpper()] = function (value, callback) {
const object = this.object;
const tid = othis.checkTransaction();
if (object[fieldName] == null) {
object[fieldName] = [];
}
object[fieldName].push(value);
othis.bimServerApi.call("LowLevelInterface", "addReference", {
tid: tid,
oid: object._i,
referenceName: fieldName,
referenceOid: value._i
}, function () {
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
if (callback != null) {
callback();
}
});
};
wrapperClass["remove" + fieldName.firstUpper()] = function (value, callback) {
const object = this.object;
const tid = othis.checkTransaction();
const list = object[fieldName];
const index = list.indexOf(value);
list.splice(index, 1);
othis.bimServerApi.call("LowLevelInterface", "removeReference", {
tid: tid,
oid: object._i,
referenceName: fieldName,
index: index
}, function () {
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
if (callback != null) {
callback();
}
});
};
wrapperClass["get" + fieldName.firstUpper()] = function (callback) {
const object = this.object;
const model = this.model;
const promise = new BimServerApiPromise();
if (object[fieldName] != null) {
if (field.many) {
object[fieldName].forEach(function (item, index) {
callback(item, index);
});
} else {
callback(object[fieldName]);
}
promise.fire();
return promise;
}
const embValue = object["_e" + fieldName];
if (embValue != null) {
if (callback != null) {
callback(embValue);
}
promise.fire();
return promise;
}
const value = object["_r" + fieldName];
if (field.many) {
if (object[fieldName] == null) {
object[fieldName] = [];
}
if (value != null) {
model.get(value, function (v) {
object[fieldName].push(v);
callback(v, object[fieldName].length - 1);
}).done(function () {
promise.fire();
});
} else {
promise.fire();
}
} else {
if (value != null) {
const ref = othis.objects[value._i];
if (value._i == -1) {
callback(null);
promise.fire();
} else if (ref == null || ref.object._s == 0) {
model.get(value._i, function (v) {
object[fieldName] = v;
callback(v);
}).done(function () {
promise.fire();
});
} else {
object[fieldName] = ref;
callback(ref);
promise.fire();
}
} else {
callback(null);
promise.fire();
}
}
return promise;
};
} else {
wrapperClass["get" + fieldName.firstUpper()] = function (callback) {
const object = this.object;
if (field.many) {
if (object[fieldName] == null) {
object[fieldName] = [];
}
// object[fieldName].push = function () {};
}
if (callback != null) {
callback(object[fieldName]);
}
return object[fieldName];
};
wrapperClass["set" + fieldName.firstUpper()] = function (value) {
const object = this.object;
object[fieldName] = value;
const tid = othis.checkTransaction();
if (field.many) {
othis.bimServerApi.call("LowLevelInterface", "setDoubleAttributes", {
tid: tid,
oid: object._i,
attributeName: fieldName,
values: value
}, function () {});
} else {
if (value == null) {
othis.bimServerApi.call("LowLevelInterface", "unsetAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName
}, function () {});
} else if (field.type === "string") {
othis.bimServerApi.call("LowLevelInterface", "setStringAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
value: value
}, function () {});
} else if (field.type === "double") {
othis.bimServerApi.call("LowLevelInterface", "setDoubleAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
value: value
}, function () {});
} else if (field.type === "boolean") {
othis.bimServerApi.call("LowLevelInterface", "setBooleanAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
value: value
}, function () {});
} else if (field.type === "int") {
othis.bimServerApi.call("LowLevelInterface", "setIntegerAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
value: value
}, function () {});
} else if (field.type === "enum") {
othis.bimServerApi.call("LowLevelInterface", "setEnumAttribute", {
tid: tid,
oid: object._i,
attributeName: fieldName,
value: value
}, function () {});
} else {
othis.bimServerApi.log("Unimplemented type " + typeof value);
}
object[fieldName] = value;
}
if (object.changedFields == null) {
object.changedFields = {};
}
object.changedFields[fieldName] = true;
othis.changedObjectOids[object.oid] = true;
};
}
})(field, fieldName);
}
}
dumpByType() {
const mapLoaded = {};
const mapNotLoaded = {};
for (let oid in this.objects) {
const object = this.objects[oid];
const type = object.getType();
const counter = mapLoaded[type];
if (object.object._s == 1) {
if (counter == null) {
mapLoaded[type] = 1;
} else {
mapLoaded[type] = counter + 1;
}
}
if (object.object._s == 0) {
const counter = mapNotLoaded[type];
if (counter == null) {
mapNotLoaded[type] = 1;
} else {
mapNotLoaded[type] = counter + 1;
}
}
}
console.log("LOADED");
for (let type in mapLoaded) {
console.log(type, mapLoaded[type]);
}
console.log("NOT_LOADED");
for (let type in mapNotLoaded) {
console.log(type, mapNotLoaded[type]);
}
}
getClass(typeName) {
const othis = this;
if (this.bimServerApi.classes[typeName] == null) {
let realType = this.bimServerApi.schemas[this.schema][typeName];
if (realType == null) {
if (typeName === "GeometryInfo" || typeName === "GeometryData") {
realType = this.bimServerApi.schemas.geometry[typeName];
}
if (realType == null) {
throw "Type " + typeName + " not found in schema " + this.schema;
}
}
const wrapperClass = {
fields: []
};
wrapperClass.isA = function (typeName) {
return othis.bimServerApi.isA(othis.schema, this.object._t, typeName);
};
wrapperClass.getType = function () {
return this.object._t;
};
wrapperClass.remove = function (removeCallback) {
const tid = othis.checkTransaction();
othis.bimServerApi.call("LowLevelInterface", "removeObject", {
tid: tid,
oid: this.object._i
}, function () {
if (removeCallback != null) {
removeCallback();
}
delete othis.objects[this.object._i];
});
};
othis.extendClass(wrapperClass, typeName);
othis.bimServerApi.classes[typeName] = wrapperClass;
}
return othis.bimServerApi.classes[typeName];
}
createWrapper(object, typeName) {
if (this.objects[object._i] != null) {
console.log("Warning!", object);
}
if (typeName == null) {
console.warn("typeName = null", object);
}
object.oid = object._i;
const cl = this.getClass(typeName);
if (cl == null) {
console.error("No class found for " + typeName);
}
const wrapper = Object.create(cl);
// transient variables
wrapper.trans = {
mode: 2
};
wrapper.oid = object.oid;
wrapper.model = this;
wrapper.object = object;
return wrapper;
}
size(callback) {
this.bimServerApi.call("ServiceInterface", "getRevision", {
roid: this.roid
}, function (revision) {
callback(revision.size);
});
}
count(type, includeAllSubTypes, callback) {
// TODO use includeAllSubTypes
this.bimServerApi.call("LowLevelInterface", "count", {
roid: this.roid,
className: type
}, function (size) {
callback(size);
});
}
getByX(methodName, keyname, fetchingMap, targetMap, query, getValueMethod, list, callback) {
const promise = new BimServerApiPromise();
if (typeof list == "string" || typeof list == "number") {
list = [list];
}
let len = list.length;
// Iterating in reverse order because we remove items from this array
while (len--) {
const item = list[len];
if (targetMap[item] != null) {
// Already loaded? Remove from list and call callback
const existingObject = targetMap[item].object;
if (existingObject._s == 1) {
const index = list.indexOf(item);
list.splice(index, 1);
callback(targetMap[item]);
}
} else if (fetchingMap[item] != null) {
// Already loading? Add the callback to the list and remove from fetching list
fetchingMap[item].push(callback);
const index = list.indexOf(item);
list.splice(index, 1);
}
}
const othis = this;
// Any left?
if (list.length > 0) {
list.forEach(function (item) {
fetchingMap[item] = [];
});
othis.bimServerApi.getJsonStreamingSerializer(function (serializer) {
const request = {
roids: [othis.roid],
query: JSON.stringify(query),
serializerOid: serializer.oid,
sync: false
};
othis.bimServerApi.call("ServiceInterface", "download", request, function (topicId) {
const url = othis.bimServerApi.generateRevisionDownloadUrl({
topicId: topicId,
serializerOid: serializer.oid
});
othis.bimServerApi.getJson(url, null, function (data) {
if (data.objects.length > 0) {
let done = 0;
data.objects.forEach(function (object) {
let wrapper = null;
if (othis.objects[object._i] != null) {
wrapper = othis.objects[object._i];
if (wrapper.object._s != 1) {
wrapper.object = object;
}
} else {
wrapper = othis.createWrapper(object, object._t);
}
const item = getValueMethod(object);
// Checking the value again, because sometimes serializers send more objects...
if (list.indexOf(item) != -1) {
targetMap[item] = wrapper;
if (fetchingMap[item] != null) {
fetchingMap[item].forEach(function (cb) {
cb(wrapper);
});
delete fetchingMap[item];
}
callback(wrapper);
}
done++;
if (done == data.objects.length) {
othis.bimServerApi.call("ServiceInterface", "cleanupLongAction", {
topicId: topicId
}, function () {
promise.fire();
});
}
});
} else {
othis.bimServerApi.log("Object with " + keyname + " " + list + " not found");
callback(null);
promise.fire();
}
}, function (error) {
console.log(error);
});
});
});
} else {
promise.fire();
}
return promise;
}
getByGuids(guids, callback) {
const query = {
guids: guids
};
return this.getByX("getByGuid", "guid", this.guidsFetching, this.objectsByGuid, query, function (object) {
return object.GlobalId;
}, guids, callback);
}
get(oids, callback) {
if (typeof oids == "number") {
oids = [oids];
} else if (typeof oids == "string") {
oids = [parseInt(oids)];
} else if (Array.isArray(oids)) {
const newOids = [];
oids.forEach(function (oid) {
if (typeof oid == "object") {
newOids.push(oid._i);
} else {
newOids.push(oid);
}
});
oids = newOids;
}
const query = {
oids: oids
};
return this.getByX("get", "OID", this.oidsFetching, this.objects, query, function (object) {
return object._i;
}, oids, callback);
}
getByName(names, callback) {
const query = {
names: names
};
return this.getByX("getByName", "name", this.namesFetching, this.objectsByName, query, function (object) {
return object.getName == null ? null : object.getName();
}, names, callback);
}
query(query, callback, errorCallback) {
const promise = new BimServerApiPromise();
const fullTypesLoading = {};
if (query.queries != null) {
query.queries.forEach((subQuery) => {
if (subQuery.type != null) {
if (typeof subQuery.type === "object") {
fullTypesLoading[subQuery.type.name] = true;
this.loadedTypes[subQuery.type.name] = {};
if (subQuery.type.includeAllSubTypes) {
const schema = this.bimServerApi.schemas[this.schema];
this.bimServerApi.getAllSubTypes(schema, subQuery.type.name, (subTypeName) => {
fullTypesLoading[subTypeName] = true;
this.loadedTypes[subTypeName] = {};
});
}
} else {
fullTypesLoading[subQuery.type] = true;
this.loadedTypes[subQuery.type] = {};
if (subQuery.includeAllSubTypes) {
const schema = this.bimServerApi.schemas[this.schema];
this.bimServerApi.getAllSubTypes(schema, subQuery.type, (subTypeName) => {
fullTypesLoading[subTypeName] = true;
this.loadedTypes[subTypeName] = {};
});
}
}
}
});
}
this.bimServerApi.getJsonStreamingSerializer((serializer) => {
this.bimServerApi.callWithFullIndication("ServiceInterface", "download", {
roids: [this.roid],
query: JSON.stringify(query),
serializerOid: serializer.oid,
sync: false
}, (topicId) => {
let handled = false;
this.bimServerApi.registerProgressHandler(topicId, (topicId, state) => {
if (state.title == "Done preparing" && !handled) {
handled = true;
const url = this.bimServerApi.generateRevisionDownloadUrl({
topicId: topicId,
serializerOid: serializer.oid
});
this.bimServerApi.notifier.setInfo(this.bimServerApi.translate("GETTING_MODEL_DATA"), -1);
this.bimServerApi.getJson(url, null, (data) => {
//console.log("query", data.objects.length);
data.objects.forEach((object) => {
let wrapper = this.objects[object._i];
if (wrapper == null) {
wrapper = this.createWrapper(object, object._t);
this.objects[object._i] = wrapper;
if (fullTypesLoading[object._t] != null) {
this.loadedTypes[object._t][wrapper.oid] = wrapper;
}
} else {
if (object._s == 1) {
wrapper.object = object;
}
}
// if (othis.loadedTypes[wrapper.getType()] == null) {
// othis.loadedTypes[wrapper.getType()] = {};
// }
// othis.loadedTypes[wrapper.getType()][object._i] = wrapper;
if (object._s == 1 && callback != null) {
callback(wrapper);
}
});
// othis.dumpByType();
this.bimServerApi.call("ServiceInterface", "cleanupLongAction", {
topicId: topicId
}, () => {
promise.fire();
this.bimServerApi.notifier.setSuccess(this.bimServerApi.translate("MODEL_DATA_DONE"));
});
});
} else if (state.state == "AS_ERROR") {
if (errorCallback != null) {
errorCallback(state.title);
} else {
console.error(state.title);
}
}
});
});
});
return promise;
}
getAllOfType(type, includeAllSubTypes, callback) {
const promise = new BimServerApiPromise();
if (this.loadedDeep) {
for (let oid in this.objects) {
const object = this.objects[oid];
if (object._t == type) {
callback(object);
}
}
promise.fire();
} else {
const types = [];
types.push(type);
if (includeAllSubTypes) {
this.bimServerApi.getAllSubTypes(this.bimServerApi.schemas[this.schema], type, function (subType) {
types.push(subType);
});
}
const query = {
queries: []
};
types.forEach((type) => {
if (this.loadedTypes[type] != null) {
for (let oid in this.loadedTypes[type]) {
callback(this.loadedTypes[type][oid]);
}
} else {
query.queries.push({
type: type
});
}
});
if (query.queries.length > 0) {
this.bimServerApi.getJsonStreamingSerializer((serializer) => {
this.bimServerApi.call("ServiceInterface", "download", {
roids: [this.roid],
query: JSON.stringify(query),
serializerOid: serializer.oid,
sync: false
}, (topicId) => {
const url = this.bimServerApi.generateRevisionDownloadUrl({
topicId: topicId,
serializerOid: serializer.oid
});
this.bimServerApi.getJson(url, null, (data) => {
if (this.loadedTypes[type] == null) {
this.loadedTypes[type] = {};
}
data.objects.some((object) => {
if (this.objects[object._i] != null) {
// Hmm we are doing a query on type, but some objects have already loaded, let's use those instead
const wrapper = this.objects[object._i];
if (wrapper.object._s == 1) {
if (wrapper.isA(type)) {
this.loadedTypes[type][object._i] = wrapper;
return callback(wrapper);
}
} else {
// Replace the value with something that's LOADED
wrapper.object = object;
if (wrapper.isA(type)) {
this.loadedTypes[type][object._i] = wrapper;
return callback(wrapper);
}
}
} else {
const wrapper = this.createWrapper(object, object._t);
this.objects[object._i] = wrapper;
if (wrapper.isA(type) && object._s == 1) {
this.loadedTypes[type][object._i] = wrapper;
return callback(wrapper);
}
}
});
this.bimServerApi.call("ServiceInterface", "cleanupLongAction", {
topicId: topicId
}, () => {
promise.fire();
});
}, (error) => {
console.log(error);
});
});
});
} else {
promise.fire();
}
}
return promise;
}
}