import {BimServerApiPromise} from "./bimserverapipromise.js"; export 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" || typeName === "Buffer") { 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" || typeName === "Buffer") { 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((t) => { if (this.loadedTypes[t] && Object.getOwnPropertyNames(this.loadedTypes[t]).length !== 0) { for (let oid in this.loadedTypes[t]) { callback(this.loadedTypes[t][oid]); } } else { query.queries.push({ type: t }); } }); 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) => { data.objects.forEach((object) => { if (!this.loadedTypes[object._t]) { this.loadedTypes[object._t] = {} } if (this.objects[object._i]) { // 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) { // Replace the value with something that's LOADED wrapper.object = object; } if (wrapper.isA(object._t)) { this.loadedTypes[object._t][object._i] = wrapper; callback(wrapper); } } else { const wrapper = this.createWrapper(object, object._t); this.objects[object._i] = wrapper; if (object._s == 1) { this.loadedTypes[object._t][object._i] = wrapper; callback(wrapper); } } }); this.bimServerApi.call("ServiceInterface", "cleanupLongAction", { topicId: topicId }, () => { promise.fire(); }); }, (error) => { console.log(error); }); }); }); } else { promise.fire(); } } return promise; } }