@xeokit/xeokit-sdk 2.3.9 → 2.4.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/xeokit-sdk.cjs.js +1274 -1440
- package/dist/xeokit-sdk.es.js +1274 -1440
- package/dist/xeokit-sdk.es5.js +130 -137
- package/dist/xeokit-sdk.min.cjs.js +2 -2
- package/dist/xeokit-sdk.min.es.js +2 -2
- package/dist/xeokit-sdk.min.es5.js +1 -1
- package/package.json +1 -1
package/dist/xeokit-sdk.cjs.js
CHANGED
|
@@ -46,7 +46,7 @@ class Map$1 {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const idMap
|
|
49
|
+
const idMap = new Map$1();
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Internal data class that represents the state of a menu or a submenu.
|
|
@@ -330,7 +330,7 @@ class ContextMenu {
|
|
|
330
330
|
*/
|
|
331
331
|
constructor(cfg = {}) {
|
|
332
332
|
|
|
333
|
-
this._id = idMap
|
|
333
|
+
this._id = idMap.addItem();
|
|
334
334
|
this._context = null;
|
|
335
335
|
this._enabled = false; // True when the ContextMenu is enabled
|
|
336
336
|
this._itemsCfg = []; // Items as given as configs
|
|
@@ -541,7 +541,7 @@ class ContextMenu {
|
|
|
541
541
|
this._context = null;
|
|
542
542
|
this._clear();
|
|
543
543
|
if (this._id !== null) {
|
|
544
|
-
idMap
|
|
544
|
+
idMap.removeItem(this._id);
|
|
545
545
|
this._id = null;
|
|
546
546
|
}
|
|
547
547
|
}
|
|
@@ -65656,7 +65656,7 @@ class Dot {
|
|
|
65656
65656
|
dotStyle.left = 0 + "px";
|
|
65657
65657
|
dotStyle["box-shadow"] = "0 2px 5px 0 #182A3D;";
|
|
65658
65658
|
dotStyle["opacity"] = 1.0;
|
|
65659
|
-
dotStyle["pointer-events"] = "
|
|
65659
|
+
dotStyle["pointer-events"] = "all";
|
|
65660
65660
|
if (cfg.onContextMenu) ;
|
|
65661
65661
|
parentElement.appendChild(dot);
|
|
65662
65662
|
|
|
@@ -80423,7 +80423,9 @@ class BCFViewpointsPlugin extends Plugin {
|
|
|
80423
80423
|
for (let id in sectionPlanes) {
|
|
80424
80424
|
if (sectionPlanes.hasOwnProperty(id)) {
|
|
80425
80425
|
let sectionPlane = sectionPlanes[id];
|
|
80426
|
-
|
|
80426
|
+
if (!sectionPlane.active) {
|
|
80427
|
+
continue;
|
|
80428
|
+
}
|
|
80427
80429
|
let location = sectionPlane.pos;
|
|
80428
80430
|
|
|
80429
80431
|
let direction;
|
|
@@ -86380,7 +86382,7 @@ class MetaModel {
|
|
|
86380
86382
|
/**
|
|
86381
86383
|
* @private
|
|
86382
86384
|
*/
|
|
86383
|
-
constructor(metaScene, id, projectId, revisionId, author, createdAt, creatingApplication, schema, propertySets
|
|
86385
|
+
constructor(metaScene, id, projectId, revisionId, author, createdAt, creatingApplication, schema, propertySets) {
|
|
86384
86386
|
|
|
86385
86387
|
/**
|
|
86386
86388
|
* Globally-unique ID.
|
|
@@ -86472,8 +86474,17 @@ class MetaModel {
|
|
|
86472
86474
|
*
|
|
86473
86475
|
* @property rootMetaObject
|
|
86474
86476
|
* @type {MetaObject}
|
|
86477
|
+
* @deprecated
|
|
86475
86478
|
*/
|
|
86476
|
-
this.rootMetaObject =
|
|
86479
|
+
this.rootMetaObject = null;
|
|
86480
|
+
|
|
86481
|
+
/**
|
|
86482
|
+
* The root {@link MetaObject}s in this MetaModel's composition structure hierarchy.
|
|
86483
|
+
*
|
|
86484
|
+
* @property rootMetaObject
|
|
86485
|
+
* @type {MetaObject[]}
|
|
86486
|
+
*/
|
|
86487
|
+
this.rootMetaObjects = [];
|
|
86477
86488
|
}
|
|
86478
86489
|
|
|
86479
86490
|
getJSON() {
|
|
@@ -86539,9 +86550,16 @@ class MetaObject {
|
|
|
86539
86550
|
*
|
|
86540
86551
|
* @property metaModel
|
|
86541
86552
|
* @type {MetaModel}
|
|
86553
|
+
* @deprecated
|
|
86542
86554
|
*/
|
|
86543
86555
|
this.metaModel = metaModel;
|
|
86544
86556
|
|
|
86557
|
+
/**
|
|
86558
|
+
* The MetaModels that share this MetaObject.
|
|
86559
|
+
* @type {MetaModel[]}
|
|
86560
|
+
*/
|
|
86561
|
+
this.metaModels = [metaModel];
|
|
86562
|
+
|
|
86545
86563
|
/**
|
|
86546
86564
|
* Globally-unique ID.
|
|
86547
86565
|
*
|
|
@@ -86901,466 +86919,17 @@ class MetaScene {
|
|
|
86901
86919
|
this.metaObjectsByType = {};
|
|
86902
86920
|
|
|
86903
86921
|
/**
|
|
86904
|
-
*
|
|
86905
|
-
* @private
|
|
86906
|
-
*/
|
|
86907
|
-
this._typeCounts = {};
|
|
86908
|
-
|
|
86909
|
-
/**
|
|
86910
|
-
* Subscriptions to events sent with {@link fire}.
|
|
86911
|
-
* @private
|
|
86912
|
-
*/
|
|
86913
|
-
this._eventSubs = {};
|
|
86914
|
-
}
|
|
86915
|
-
|
|
86916
|
-
/**
|
|
86917
|
-
* Subscribes to an event fired at this Viewer.
|
|
86918
|
-
*
|
|
86919
|
-
* @param {String} event The event
|
|
86920
|
-
* @param {Function} callback Callback fired on the event
|
|
86921
|
-
*/
|
|
86922
|
-
on(event, callback) {
|
|
86923
|
-
let subs = this._eventSubs[event];
|
|
86924
|
-
if (!subs) {
|
|
86925
|
-
subs = [];
|
|
86926
|
-
this._eventSubs[event] = subs;
|
|
86927
|
-
}
|
|
86928
|
-
subs.push(callback);
|
|
86929
|
-
}
|
|
86930
|
-
|
|
86931
|
-
/**
|
|
86932
|
-
* Fires an event at this Viewer.
|
|
86933
|
-
*
|
|
86934
|
-
* @param {String} event Event name
|
|
86935
|
-
* @param {Object} value Event parameters
|
|
86936
|
-
*/
|
|
86937
|
-
fire(event, value) {
|
|
86938
|
-
const subs = this._eventSubs[event];
|
|
86939
|
-
if (subs) {
|
|
86940
|
-
for (let i = 0, len = subs.length; i < len; i++) {
|
|
86941
|
-
subs[i](value);
|
|
86942
|
-
}
|
|
86943
|
-
}
|
|
86944
|
-
}
|
|
86945
|
-
|
|
86946
|
-
/**
|
|
86947
|
-
* Unsubscribes from an event fired at this Viewer.
|
|
86948
|
-
* @param event
|
|
86949
|
-
*/
|
|
86950
|
-
off(event) { // TODO
|
|
86951
|
-
|
|
86952
|
-
}
|
|
86953
|
-
|
|
86954
|
-
/**
|
|
86955
|
-
* Creates a {@link MetaModel} in this MetaScene.
|
|
86956
|
-
*
|
|
86957
|
-
* The MetaModel will contain a hierarchy of {@link MetaObject}s, created from the
|
|
86958
|
-
* meta objects in ````metaModelData````.
|
|
86959
|
-
*
|
|
86960
|
-
* The meta object hierarchy in ````metaModelData```` is expected to be non-cyclic, with a single root. If the meta
|
|
86961
|
-
* objects are cyclic, then this method will log an error and attempt to recover by creating a dummy root MetaObject
|
|
86962
|
-
* of type "Model" and connecting all other MetaObjects as its direct children. If the meta objects contain multiple
|
|
86963
|
-
* roots, then this method similarly attempts to recover by creating a dummy root MetaObject of type "Model" and
|
|
86964
|
-
* connecting all the root MetaObjects as its children.
|
|
86965
|
-
*
|
|
86966
|
-
* @param {String} modelId ID for the new {@link MetaModel}, which will have {@link MetaModel#id} set to this value.
|
|
86967
|
-
* @param {Object} metaModelData Data for the {@link MetaModel}.
|
|
86968
|
-
* @param {Object} [options] Options for creating the {@link MetaModel}.
|
|
86969
|
-
* @param {Object} [options.includeTypes] When provided, only create {@link MetaObject}s with types in this list.
|
|
86970
|
-
* @param {Object} [options.excludeTypes] When provided, never create {@link MetaObject}s with types in this list.
|
|
86971
|
-
* @param {Boolean} [options.globalizeObjectIds=false] Whether to globalize each {@link MetaObject#id}. Set this ````true```` when you need to load multiple instances of the same meta model, to avoid ID clashes between the meta objects in the different instances.
|
|
86972
|
-
* @returns {MetaModel} The new MetaModel.
|
|
86973
|
-
*/
|
|
86974
|
-
createMetaModel(modelId, metaModelData, options = {}) {
|
|
86975
|
-
|
|
86976
|
-
const projectId = metaModelData.projectId || "none";
|
|
86977
|
-
const revisionId = metaModelData.revisionId || "none";
|
|
86978
|
-
const newPropertySets = metaModelData.propertySets || [];
|
|
86979
|
-
const newObjects = metaModelData.metaObjects || [];
|
|
86980
|
-
const author = metaModelData.author;
|
|
86981
|
-
const createdAt = metaModelData.createdAt;
|
|
86982
|
-
const creatingApplication = metaModelData.creatingApplication;
|
|
86983
|
-
const schema = metaModelData.schema;
|
|
86984
|
-
// if (options.excludeTypes) {
|
|
86985
|
-
// excludeTypes = {};
|
|
86986
|
-
// for (let i = 0, len = options.excludeTypes.length; i < len; i++) {
|
|
86987
|
-
// includeTypes[options.excludeTypes[i]] = true;
|
|
86988
|
-
// }
|
|
86989
|
-
// }
|
|
86990
|
-
|
|
86991
|
-
const metaModel = new MetaModel(this, modelId, projectId, revisionId, author, createdAt, creatingApplication, schema, [], null);
|
|
86992
|
-
|
|
86993
|
-
this.metaModels[modelId] = metaModel;
|
|
86994
|
-
|
|
86995
|
-
for (let i = 0, len = newPropertySets.length; i < len; i++) {
|
|
86996
|
-
const propertySetCfg = newPropertySets[i];
|
|
86997
|
-
const propertySetId = propertySetCfg.id;
|
|
86998
|
-
const propertySet = new PropertySet(propertySetId, propertySetCfg.originalSystemId, propertySetCfg.name, propertySetCfg.type, propertySetCfg.properties);
|
|
86999
|
-
metaModel.propertySets[propertySetId] = propertySet;
|
|
87000
|
-
this.propertySets[propertySetId] = propertySet;
|
|
87001
|
-
}
|
|
87002
|
-
|
|
87003
|
-
const rootMetaObjects = [];
|
|
87004
|
-
|
|
87005
|
-
for (let i = 0, len = newObjects.length; i < len; i++) {
|
|
87006
|
-
const newObject = newObjects[i];
|
|
87007
|
-
if (newObject.parent === undefined || newObject.parent === null) {
|
|
87008
|
-
rootMetaObjects.push(newObject);
|
|
87009
|
-
}
|
|
87010
|
-
}
|
|
87011
|
-
|
|
87012
|
-
if (rootMetaObjects.length === 0) {
|
|
87013
|
-
this.scene.error("Cyclic containment hierarchy found in metamodel - will flatten the hierarchy and insert fake 'Model' root");
|
|
87014
|
-
const fakeRoot = {
|
|
87015
|
-
"id": modelId + ".fakeRoot",
|
|
87016
|
-
"name": modelId,
|
|
87017
|
-
"type": "Model",
|
|
87018
|
-
"parent": null
|
|
87019
|
-
};
|
|
87020
|
-
for (let i = 0, len = newObjects.length; i < len; i++) {
|
|
87021
|
-
newObjects[i].parent = fakeRoot.id;
|
|
87022
|
-
}
|
|
87023
|
-
newObjects.push(fakeRoot);
|
|
87024
|
-
}
|
|
87025
|
-
|
|
87026
|
-
if (rootMetaObjects.length > 1) {
|
|
87027
|
-
this.scene.error("Multiple containment hierarchy root found in metamodel - will insert fake 'Model' root");
|
|
87028
|
-
const fakeRoot = {
|
|
87029
|
-
"id": modelId + ".fakeRoot",
|
|
87030
|
-
"name": modelId,
|
|
87031
|
-
"type": "Model",
|
|
87032
|
-
"parent": null
|
|
87033
|
-
};
|
|
87034
|
-
newObjects.push(fakeRoot);
|
|
87035
|
-
for (let i = 0, len = rootMetaObjects.length; i < len; i++) {
|
|
87036
|
-
rootMetaObjects[i].parent = fakeRoot.id;
|
|
87037
|
-
}
|
|
87038
|
-
}
|
|
87039
|
-
|
|
87040
|
-
for (let i = 0, len = newObjects.length; i < len; i++) {
|
|
87041
|
-
const newObject = newObjects[i];
|
|
87042
|
-
const type = newObject.type;
|
|
87043
|
-
const objectId = options.globalizeObjectIds ? math.globalizeObjectId(modelId, newObject.id) : newObject.id;
|
|
87044
|
-
const originalSystemId = newObject.id;
|
|
87045
|
-
const name = newObject.name;
|
|
87046
|
-
const propertySets = [];
|
|
87047
|
-
if (newObject.propertySetIds && newObject.propertySetIds.length > 0) {
|
|
87048
|
-
for (let j = 0, lenj = newObject.propertySetIds.length; j < lenj; j++) {
|
|
87049
|
-
const propertySetId = newObject.propertySetIds[j];
|
|
87050
|
-
const propertySet = metaModel.propertySets[propertySetId];
|
|
87051
|
-
if (propertySet) {
|
|
87052
|
-
propertySets.push(propertySet);
|
|
87053
|
-
}
|
|
87054
|
-
}
|
|
87055
|
-
}
|
|
87056
|
-
const parent = null;
|
|
87057
|
-
const children = null;
|
|
87058
|
-
const external = newObject.external;
|
|
87059
|
-
const metaObject = new MetaObject(metaModel, objectId, originalSystemId, name, type, propertySets, parent, children, external);
|
|
87060
|
-
this.metaObjects[objectId] = metaObject;
|
|
87061
|
-
(this.metaObjectsByType[type] || (this.metaObjectsByType[type] = {}))[objectId] = metaObject;
|
|
87062
|
-
if (this._typeCounts[type] === undefined) {
|
|
87063
|
-
this._typeCounts[type] = 1;
|
|
87064
|
-
} else {
|
|
87065
|
-
this._typeCounts[type]++;
|
|
87066
|
-
}
|
|
87067
|
-
}
|
|
87068
|
-
|
|
87069
|
-
for (let i = 0, len = newObjects.length; i < len; i++) {
|
|
87070
|
-
const newObject = newObjects[i];
|
|
87071
|
-
const objectId = options.globalizeObjectIds ? math.globalizeObjectId(modelId, newObject.id) : newObject.id;
|
|
87072
|
-
const metaObject = this.metaObjects[objectId];
|
|
87073
|
-
if (!metaObject) {
|
|
87074
|
-
continue;
|
|
87075
|
-
}
|
|
87076
|
-
if (newObject.parent === undefined || newObject.parent === null) {
|
|
87077
|
-
metaModel.rootMetaObject = metaObject;
|
|
87078
|
-
} else if (newObject.parent) {
|
|
87079
|
-
const parentId = options.globalizeObjectIds ? math.globalizeObjectId(modelId, newObject.parent) : newObject.parent;
|
|
87080
|
-
let parentMetaObject = this.metaObjects[parentId];
|
|
87081
|
-
if (parentMetaObject) {
|
|
87082
|
-
metaObject.parent = parentMetaObject;
|
|
87083
|
-
parentMetaObject.children = parentMetaObject.children || [];
|
|
87084
|
-
parentMetaObject.children.push(metaObject);
|
|
87085
|
-
}
|
|
87086
|
-
}
|
|
87087
|
-
}
|
|
87088
|
-
|
|
87089
|
-
this.fire("metaModelCreated", modelId);
|
|
87090
|
-
return metaModel;
|
|
87091
|
-
}
|
|
87092
|
-
|
|
87093
|
-
/**
|
|
87094
|
-
* Removes a {@link MetaModel} from this MetaScene.
|
|
87095
|
-
*
|
|
87096
|
-
* Fires a "metaModelDestroyed" event with the value of the {@link MetaModel#id}.
|
|
87097
|
-
*
|
|
87098
|
-
* @param {String} id ID of the target {@link MetaModel}.
|
|
87099
|
-
*/
|
|
87100
|
-
destroyMetaModel(id) {
|
|
87101
|
-
const metaModel = this.metaModels[id];
|
|
87102
|
-
if (!metaModel) {
|
|
87103
|
-
return;
|
|
87104
|
-
}
|
|
87105
|
-
this._removeMetaModel(metaModel);
|
|
87106
|
-
this.fire("metaModelDestroyed", id);
|
|
87107
|
-
}
|
|
87108
|
-
|
|
87109
|
-
_removeMetaModel(metaModel) {
|
|
87110
|
-
const metaObjects = this.metaObjects;
|
|
87111
|
-
const metaObjectsByType = this.metaObjectsByType;
|
|
87112
|
-
let visit = (metaObject) => {
|
|
87113
|
-
delete metaObjects[metaObject.id];
|
|
87114
|
-
const types = metaObjectsByType[metaObject.type];
|
|
87115
|
-
if (types && types[metaObject.id]) {
|
|
87116
|
-
delete types[metaObject.id];
|
|
87117
|
-
if (--this._typeCounts[metaObject.type] === 0) {
|
|
87118
|
-
delete this._typeCounts[metaObject.type];
|
|
87119
|
-
delete metaObjectsByType[metaObject.type];
|
|
87120
|
-
}
|
|
87121
|
-
}
|
|
87122
|
-
const children = metaObject.children;
|
|
87123
|
-
if (children) {
|
|
87124
|
-
for (let i = 0, len = children.length; i < len; i++) {
|
|
87125
|
-
const childMetaObject = children[i];
|
|
87126
|
-
visit(childMetaObject);
|
|
87127
|
-
}
|
|
87128
|
-
}
|
|
87129
|
-
};
|
|
87130
|
-
visit(metaModel.rootMetaObject);
|
|
87131
|
-
for (let propertySetId in metaModel.propertySets) {
|
|
87132
|
-
if (metaModel.propertySets.hasOwnProperty(propertySetId)) {
|
|
87133
|
-
delete this.propertySets[propertySetId];
|
|
87134
|
-
}
|
|
87135
|
-
}
|
|
87136
|
-
delete this.metaModels[metaModel.id];
|
|
87137
|
-
}
|
|
87138
|
-
|
|
87139
|
-
/**
|
|
87140
|
-
* Gets the {@link MetaObject#id}s of the {@link MetaObject}s that have the given {@link MetaObject#type}.
|
|
87141
|
-
*
|
|
87142
|
-
* @param {String} type The type.
|
|
87143
|
-
* @returns {String[]} Array of {@link MetaObject#id}s.
|
|
87144
|
-
*/
|
|
87145
|
-
getObjectIDsByType(type) {
|
|
87146
|
-
const metaObjects = this.metaObjectsByType[type];
|
|
87147
|
-
return metaObjects ? Object.keys(metaObjects) : [];
|
|
87148
|
-
}
|
|
87149
|
-
|
|
87150
|
-
/**
|
|
87151
|
-
* Gets the {@link MetaObject#id}s of the {@link MetaObject}s within the given subtree.
|
|
87152
|
-
*
|
|
87153
|
-
* @param {String} id ID of the root {@link MetaObject} of the given subtree.
|
|
87154
|
-
* @param {String[]} [includeTypes] Optional list of types to include.
|
|
87155
|
-
* @param {String[]} [excludeTypes] Optional list of types to exclude.
|
|
87156
|
-
* @returns {String[]} Array of {@link MetaObject#id}s.
|
|
87157
|
-
*/
|
|
87158
|
-
getObjectIDsInSubtree(id, includeTypes, excludeTypes) {
|
|
87159
|
-
const list = [];
|
|
87160
|
-
const metaObject = this.metaObjects[id];
|
|
87161
|
-
const includeMask = (includeTypes && includeTypes.length > 0) ? arrayToMap(includeTypes) : null;
|
|
87162
|
-
const excludeMask = (excludeTypes && excludeTypes.length > 0) ? arrayToMap(excludeTypes) : null;
|
|
87163
|
-
|
|
87164
|
-
function visit(metaObject) {
|
|
87165
|
-
if (!metaObject) {
|
|
87166
|
-
return;
|
|
87167
|
-
}
|
|
87168
|
-
var include = true;
|
|
87169
|
-
if (excludeMask && excludeMask[metaObject.type]) {
|
|
87170
|
-
include = false;
|
|
87171
|
-
} else if (includeMask && (!includeMask[metaObject.type])) {
|
|
87172
|
-
include = false;
|
|
87173
|
-
}
|
|
87174
|
-
if (include) {
|
|
87175
|
-
list.push(metaObject.id);
|
|
87176
|
-
}
|
|
87177
|
-
const children = metaObject.children;
|
|
87178
|
-
if (children) {
|
|
87179
|
-
for (var i = 0, len = children.length; i < len; i++) {
|
|
87180
|
-
visit(children[i]);
|
|
87181
|
-
}
|
|
87182
|
-
}
|
|
87183
|
-
}
|
|
87184
|
-
|
|
87185
|
-
visit(metaObject);
|
|
87186
|
-
return list;
|
|
87187
|
-
}
|
|
87188
|
-
|
|
87189
|
-
/**
|
|
87190
|
-
* Iterates over the {@link MetaObject}s within the subtree.
|
|
87191
|
-
*
|
|
87192
|
-
* @param {String} id ID of root {@link MetaObject}.
|
|
87193
|
-
* @param {Function} callback Callback fired at each {@link MetaObject}.
|
|
87194
|
-
*/
|
|
87195
|
-
withMetaObjectsInSubtree(id, callback) {
|
|
87196
|
-
const metaObject = this.metaObjects[id];
|
|
87197
|
-
if (!metaObject) {
|
|
87198
|
-
return;
|
|
87199
|
-
}
|
|
87200
|
-
metaObject.withMetaObjectsInSubtree(callback);
|
|
87201
|
-
}
|
|
87202
|
-
}
|
|
87203
|
-
|
|
87204
|
-
function arrayToMap(array) {
|
|
87205
|
-
const map = {};
|
|
87206
|
-
for (var i = 0, len = array.length; i < len; i++) {
|
|
87207
|
-
map[array[i]] = true;
|
|
87208
|
-
}
|
|
87209
|
-
return map;
|
|
87210
|
-
}
|
|
87211
|
-
|
|
87212
|
-
/**
|
|
87213
|
-
* The 3D Viewer at the heart of the xeokit SDK.
|
|
87214
|
-
*
|
|
87215
|
-
* * A Viewer wraps a single {@link Scene}
|
|
87216
|
-
* * Add {@link Plugin}s to a Viewer to extend its functionality.
|
|
87217
|
-
* * {@link Viewer#metaScene} holds metadata about models in the
|
|
87218
|
-
* Viewer's {@link MetaScene}.
|
|
87219
|
-
* * Use {@link Viewer#cameraFlight} to fly or jump the {@link Scene}'s
|
|
87220
|
-
* {@link Camera} to target positions, boundaries or {@link Entity}s.
|
|
87221
|
-
*
|
|
87222
|
-
* @public
|
|
87223
|
-
*/
|
|
87224
|
-
class Viewer {
|
|
87225
|
-
|
|
87226
|
-
/**
|
|
87227
|
-
* @constructor
|
|
87228
|
-
* @param {Object} cfg Viewer configuration.
|
|
87229
|
-
* @param {String} [cfg.id] Optional ID for this Viewer, defaults to the ID of {@link Viewer#scene}, which xeokit automatically generates.
|
|
87230
|
-
* @param {String} [cfg.canvasId] ID of an existing HTML canvas for the {@link Viewer#scene} - either this or canvasElement is mandatory. When both values are given, the element reference is always preferred to the ID.
|
|
87231
|
-
* @param {HTMLCanvasElement} [cfg.canvasElement] Reference of an existing HTML canvas for the {@link Viewer#scene} - either this or canvasId is mandatory. When both values are given, the element reference is always preferred to the ID.
|
|
87232
|
-
* @param {HTMLElement} [cfg.keyboardEventsElement] Optional reference to HTML element on which key events should be handled. Defaults to the HTML Document.
|
|
87233
|
-
* @param {String} [cfg.spinnerElementId] ID of existing HTML element to show the {@link Spinner} - internally creates a default element automatically if this is omitted.
|
|
87234
|
-
* @param {Number} [cfg.passes=1] The number of times the {@link Viewer#scene} renders per frame.
|
|
87235
|
-
* @param {Boolean} [cfg.clearEachPass=false] When doing multiple passes per frame, specifies if to clear the canvas before each pass (true) or just before the first pass (false).
|
|
87236
|
-
* @param {Boolean} [cfg.preserveDrawingBuffer=true] Whether or not to preserve the WebGL drawing buffer. This needs to be ````true```` for {@link Viewer#getSnapshot} to work.
|
|
87237
|
-
* @param {Boolean} [cfg.transparent=true] Whether or not the canvas is transparent.
|
|
87238
|
-
* @param {Boolean} [cfg.premultipliedAlpha=false] Whether or not you want alpha composition with premultiplied alpha. Highlighting and selection works best when this is ````false````.
|
|
87239
|
-
* @param {Boolean} [cfg.gammaInput=true] When true, expects that all textures and colors are premultiplied gamma.
|
|
87240
|
-
* @param {Boolean} [cfg.gammaOutput=false] Whether or not to render with pre-multiplied gama.
|
|
87241
|
-
* @param {Number} [cfg.gammaFactor=2.2] The gamma factor to use when rendering with pre-multiplied gamma.
|
|
87242
|
-
* @param {Number[]} [cfg.backgroundColor=[1,1,1]] Sets the canvas background color to use when ````transparent```` is false.
|
|
87243
|
-
* @param {Boolean} [cfg.backgroundColorFromAmbientLight=true] When ````transparent```` is false, set this ````true````
|
|
87244
|
-
* to derive the canvas background color from {@link AmbientLight#color}, or ````false```` to set the canvas background to ````backgroundColor````.
|
|
87245
|
-
* @param {String} [cfg.units="meters"] The measurement unit type. Accepted values are ````"meters"````, ````"metres"````, , ````"centimeters"````, ````"centimetres"````, ````"millimeters"````, ````"millimetres"````, ````"yards"````, ````"feet"```` and ````"inches"````.
|
|
87246
|
-
* @param {Number} [cfg.scale=1] The number of Real-space units in each World-space coordinate system unit.
|
|
87247
|
-
* @param {Number[]} [cfg.origin=[0,0,0]] The Real-space 3D origin, in current measurement units, at which the World-space coordinate origin ````[0,0,0]```` sits.
|
|
87248
|
-
* @param {Boolean} [cfg.saoEnabled=false] Whether to enable Scalable Ambient Obscurance (SAO) effect. See {@link SAO} for more info.
|
|
87249
|
-
* @param {Boolean} [cfg.antialias=true] Whether to enable anti-aliasing.
|
|
87250
|
-
* @throws {String} Throws an exception when both canvasId or canvasElement are missing or they aren't pointing to a valid HTMLCanvasElement.
|
|
87251
|
-
* @param {Boolean} [cfg.alphaDepthMask=true] Whether writing into the depth buffer is enabled or disabled when rendering transparent objects.
|
|
87252
|
-
* @param {Boolean} [cfg.entityOffsetsEnabled=false] Whether to enable {@link Entity#offset}. For best performance, only set this ````true```` when you need to use {@link Entity#offset}.
|
|
87253
|
-
* @param {Boolean} [cfg.pickSurfacePrecisionEnabled=false] Whether to enable full-precision accuracy when surface picking with {@link Scene#pick}. Note that when ````true````, this configuration will increase the amount of browser memory used by the Viewer. The ````pickSurfacePrecision```` option for ````Scene#pick```` only works if this is set ````true````.
|
|
87254
|
-
* @param {Boolean} [cfg.logarithmicDepthBufferEnabled=false] Whether to enable logarithmic depth buffer. When this is true,
|
|
87255
|
-
* you can set huge values for {@link Perspective#far} and {@link Ortho#far}, to push the far clipping plane back so
|
|
87256
|
-
* that it does not clip huge models.
|
|
87257
|
-
* @param {Boolean} [cfg.colorTextureEnabled=true] Whether to enable base color texture rendering.
|
|
87258
|
-
* @param {Boolean} [cfg.pbrEnabled=false] Whether to enable physically-based rendering.
|
|
87259
|
-
* @param {LocaleService} [cfg.localeService=null] Optional locale-based translation service.
|
|
87260
|
-
*/
|
|
87261
|
-
constructor(cfg) {
|
|
87262
|
-
|
|
87263
|
-
/**
|
|
87264
|
-
* The Viewer's current language setting.
|
|
87265
|
-
* @property language
|
|
87266
|
-
* @deprecated
|
|
87267
|
-
* @type {String}
|
|
87268
|
-
*/
|
|
87269
|
-
this.language = "en";
|
|
87270
|
-
|
|
87271
|
-
/**
|
|
87272
|
-
* The viewer's locale service.
|
|
87273
|
-
*
|
|
87274
|
-
* This is configured via the Viewer's constructor.
|
|
86922
|
+
* The root {@link MetaObject}s belonging to this MetaScene, each mapped to its {@link MetaObject#id}.
|
|
87275
86923
|
*
|
|
87276
|
-
*
|
|
87277
|
-
* null translations for all given strings and phrases.
|
|
87278
|
-
*
|
|
87279
|
-
* @property localeService
|
|
87280
|
-
* @type {LocaleService}
|
|
87281
|
-
* @since 2.0
|
|
87282
|
-
*/
|
|
87283
|
-
this.localeService = cfg.localeService || new LocaleService();
|
|
87284
|
-
|
|
87285
|
-
/**
|
|
87286
|
-
* The Viewer's {@link Scene}.
|
|
87287
|
-
* @property scene
|
|
87288
|
-
* @type {Scene}
|
|
87289
|
-
*/
|
|
87290
|
-
this.scene = new Scene(this, {
|
|
87291
|
-
canvasId: cfg.canvasId,
|
|
87292
|
-
canvasElement: cfg.canvasElement,
|
|
87293
|
-
keyboardEventsElement: cfg.keyboardEventsElement,
|
|
87294
|
-
contextAttr: {
|
|
87295
|
-
preserveDrawingBuffer: cfg.preserveDrawingBuffer !== false,
|
|
87296
|
-
premultipliedAlpha: (!!cfg.premultipliedAlpha),
|
|
87297
|
-
antialias: (cfg.antialias !== false)
|
|
87298
|
-
},
|
|
87299
|
-
spinnerElementId: cfg.spinnerElementId,
|
|
87300
|
-
transparent: (cfg.transparent !== false),
|
|
87301
|
-
gammaInput: true,
|
|
87302
|
-
gammaOutput: false,
|
|
87303
|
-
backgroundColor: cfg.backgroundColor,
|
|
87304
|
-
backgroundColorFromAmbientLight: cfg.backgroundColorFromAmbientLight,
|
|
87305
|
-
ticksPerRender: 1,
|
|
87306
|
-
ticksPerOcclusionTest: 20,
|
|
87307
|
-
units: cfg.units,
|
|
87308
|
-
scale: cfg.scale,
|
|
87309
|
-
origin: cfg.origin,
|
|
87310
|
-
saoEnabled: cfg.saoEnabled,
|
|
87311
|
-
alphaDepthMask: (cfg.alphaDepthMask !== false),
|
|
87312
|
-
entityOffsetsEnabled: (!!cfg.entityOffsetsEnabled),
|
|
87313
|
-
pickSurfacePrecisionEnabled: (!!cfg.pickSurfacePrecisionEnabled),
|
|
87314
|
-
logarithmicDepthBufferEnabled: (!!cfg.logarithmicDepthBufferEnabled),
|
|
87315
|
-
pbrEnabled: (!!cfg.pbrEnabled),
|
|
87316
|
-
colorTextureEnabled: (cfg.colorTextureEnabled !== false)
|
|
87317
|
-
});
|
|
87318
|
-
|
|
87319
|
-
/**
|
|
87320
|
-
* Metadata about the {@link Scene} and the models and objects within it.
|
|
87321
|
-
* @property metaScene
|
|
87322
|
-
* @type {MetaScene}
|
|
87323
|
-
* @readonly
|
|
87324
|
-
*/
|
|
87325
|
-
this.metaScene = new MetaScene(this, this.scene);
|
|
87326
|
-
|
|
87327
|
-
/**
|
|
87328
|
-
* The Viewer's ID.
|
|
87329
|
-
* @property id
|
|
87330
|
-
*
|
|
87331
|
-
* @type {String|Number}
|
|
87332
|
-
*/
|
|
87333
|
-
this.id = cfg.id || this.scene.id;
|
|
87334
|
-
|
|
87335
|
-
/**
|
|
87336
|
-
* The Viewer's {@link Camera}. This is also found on {@link Scene#camera}.
|
|
87337
|
-
* @property camera
|
|
87338
|
-
* @type {Camera}
|
|
87339
|
-
*/
|
|
87340
|
-
this.camera = this.scene.camera;
|
|
87341
|
-
|
|
87342
|
-
/**
|
|
87343
|
-
* The Viewer's {@link CameraFlightAnimation}, which
|
|
87344
|
-
* is used to fly the {@link Scene}'s {@link Camera} to given targets.
|
|
87345
|
-
* @property cameraFlight
|
|
87346
|
-
* @type {CameraFlightAnimation}
|
|
86924
|
+
* @type {{String:MetaObject}}
|
|
87347
86925
|
*/
|
|
87348
|
-
this.
|
|
87349
|
-
duration: 0.5
|
|
87350
|
-
});
|
|
86926
|
+
this.rootMetaObjects = {};
|
|
87351
86927
|
|
|
87352
86928
|
/**
|
|
87353
|
-
*
|
|
87354
|
-
*
|
|
87355
|
-
* @property cameraControl
|
|
87356
|
-
* @type {CameraControl}
|
|
86929
|
+
* Tracks number of MetaObjects of each type.
|
|
86930
|
+
* @private
|
|
87357
86931
|
*/
|
|
87358
|
-
this.
|
|
87359
|
-
// panToPointer: true,
|
|
87360
|
-
doublePickFlyTo: true
|
|
87361
|
-
});
|
|
87362
|
-
|
|
87363
|
-
this._plugins = [];
|
|
86932
|
+
this._typeCounts = {};
|
|
87364
86933
|
|
|
87365
86934
|
/**
|
|
87366
86935
|
* Subscriptions to events sent with {@link fire}.
|
|
@@ -87369,15 +86938,467 @@ class Viewer {
|
|
|
87369
86938
|
this._eventSubs = {};
|
|
87370
86939
|
}
|
|
87371
86940
|
|
|
87372
|
-
/**
|
|
87373
|
-
*
|
|
87374
|
-
*
|
|
87375
|
-
* @
|
|
87376
|
-
|
|
87377
|
-
|
|
87378
|
-
|
|
87379
|
-
|
|
87380
|
-
|
|
86941
|
+
/**
|
|
86942
|
+
* Subscribes to an event fired at this Viewer.
|
|
86943
|
+
*
|
|
86944
|
+
* @param {String} event The event
|
|
86945
|
+
* @param {Function} callback Callback fired on the event
|
|
86946
|
+
*/
|
|
86947
|
+
on(event, callback) {
|
|
86948
|
+
let subs = this._eventSubs[event];
|
|
86949
|
+
if (!subs) {
|
|
86950
|
+
subs = [];
|
|
86951
|
+
this._eventSubs[event] = subs;
|
|
86952
|
+
}
|
|
86953
|
+
subs.push(callback);
|
|
86954
|
+
}
|
|
86955
|
+
|
|
86956
|
+
/**
|
|
86957
|
+
* Fires an event at this Viewer.
|
|
86958
|
+
*
|
|
86959
|
+
* @param {String} event Event name
|
|
86960
|
+
* @param {Object} value Event parameters
|
|
86961
|
+
*/
|
|
86962
|
+
fire(event, value) {
|
|
86963
|
+
const subs = this._eventSubs[event];
|
|
86964
|
+
if (subs) {
|
|
86965
|
+
for (let i = 0, len = subs.length; i < len; i++) {
|
|
86966
|
+
subs[i](value);
|
|
86967
|
+
}
|
|
86968
|
+
}
|
|
86969
|
+
}
|
|
86970
|
+
|
|
86971
|
+
/**
|
|
86972
|
+
* Unsubscribes from an event fired at this Viewer.
|
|
86973
|
+
* @param event
|
|
86974
|
+
*/
|
|
86975
|
+
off(event) { // TODO
|
|
86976
|
+
|
|
86977
|
+
}
|
|
86978
|
+
|
|
86979
|
+
/**
|
|
86980
|
+
* Creates a {@link MetaModel} in this MetaScene.
|
|
86981
|
+
*
|
|
86982
|
+
* The MetaModel will contain a hierarchy of {@link MetaObject}s, created from the
|
|
86983
|
+
* meta objects in ````metaModelData````.
|
|
86984
|
+
*
|
|
86985
|
+
* The meta object hierarchy in ````metaModelData```` is expected to be non-cyclic, with a single root. If the meta
|
|
86986
|
+
* objects are cyclic, then this method will log an error and attempt to recover by creating a dummy root MetaObject
|
|
86987
|
+
* of type "Model" and connecting all other MetaObjects as its direct children. If the meta objects contain multiple
|
|
86988
|
+
* roots, then this method similarly attempts to recover by creating a dummy root MetaObject of type "Model" and
|
|
86989
|
+
* connecting all the root MetaObjects as its children.
|
|
86990
|
+
*
|
|
86991
|
+
* @param {String} modelId ID for the new {@link MetaModel}, which will have {@link MetaModel#id} set to this value.
|
|
86992
|
+
* @param {Object} metaModelData Data for the {@link MetaModel}.
|
|
86993
|
+
* @param {Object} [options] Options for creating the {@link MetaModel}.
|
|
86994
|
+
* @param {Object} [options.includeTypes] When provided, only create {@link MetaObject}s with types in this list.
|
|
86995
|
+
* @param {Object} [options.excludeTypes] When provided, never create {@link MetaObject}s with types in this list.
|
|
86996
|
+
* @param {Boolean} [options.globalizeObjectIds=false] Whether to globalize each {@link MetaObject#id}. Set this ````true```` when you need to load multiple instances of the same meta model, to avoid ID clashes between the meta objects in the different instances.
|
|
86997
|
+
* @returns {MetaModel} The new MetaModel.
|
|
86998
|
+
*/
|
|
86999
|
+
createMetaModel(modelId, metaModelData, options = {}) {
|
|
87000
|
+
|
|
87001
|
+
const projectId = metaModelData.projectId || "none";
|
|
87002
|
+
const revisionId = metaModelData.revisionId || "none";
|
|
87003
|
+
const newPropertySets = metaModelData.propertySets || [];
|
|
87004
|
+
const newObjects = metaModelData.metaObjects || [];
|
|
87005
|
+
const author = metaModelData.author;
|
|
87006
|
+
const createdAt = metaModelData.createdAt;
|
|
87007
|
+
const creatingApplication = metaModelData.creatingApplication;
|
|
87008
|
+
const schema = metaModelData.schema;
|
|
87009
|
+
// if (options.excludeTypes) {
|
|
87010
|
+
// excludeTypes = {};
|
|
87011
|
+
// for (let i = 0, len = options.excludeTypes.length; i < len; i++) {
|
|
87012
|
+
// includeTypes[options.excludeTypes[i]] = true;
|
|
87013
|
+
// }
|
|
87014
|
+
// }
|
|
87015
|
+
|
|
87016
|
+
const metaModel = new MetaModel(this, modelId, projectId, revisionId, author, createdAt, creatingApplication, schema, [], null);
|
|
87017
|
+
|
|
87018
|
+
this.metaModels[modelId] = metaModel;
|
|
87019
|
+
|
|
87020
|
+
for (let i = 0, len = newPropertySets.length; i < len; i++) {
|
|
87021
|
+
const propertySetCfg = newPropertySets[i];
|
|
87022
|
+
const propertySetId = propertySetCfg.id;
|
|
87023
|
+
const propertySet = new PropertySet(propertySetId, propertySetCfg.originalSystemId, propertySetCfg.name, propertySetCfg.type, propertySetCfg.properties);
|
|
87024
|
+
metaModel.propertySets[propertySetId] = propertySet;
|
|
87025
|
+
this.propertySets[propertySetId] = propertySet;
|
|
87026
|
+
}
|
|
87027
|
+
|
|
87028
|
+
const filteredObjectsParams = []; // Params for each new metaobject that passes our filters
|
|
87029
|
+
const existingObjects = []; // List of our metaobjects that are already existing, to which we'll also append new metaobjects we'll create
|
|
87030
|
+
const createObjectsParams = []; // List of params for metaobjects we'll create
|
|
87031
|
+
|
|
87032
|
+
|
|
87033
|
+
// Filter out the parameters we'll create
|
|
87034
|
+
|
|
87035
|
+
for (let i = 0, len = newObjects.length; i < len; i++) {
|
|
87036
|
+
const newObject = newObjects[i];
|
|
87037
|
+
newObject.type;
|
|
87038
|
+
filteredObjectsParams.push(newObject);
|
|
87039
|
+
}
|
|
87040
|
+
|
|
87041
|
+
// Build list of params for metaobjects we'll create
|
|
87042
|
+
|
|
87043
|
+
for (let i = 0, len = filteredObjectsParams.length; i < len; i++) {
|
|
87044
|
+
let filteredObject = filteredObjectsParams[i];
|
|
87045
|
+
const existingObject = this.metaObjects[filteredObject.id];
|
|
87046
|
+
if (existingObject) {
|
|
87047
|
+
existingObject.metaModels.push(metaModel);
|
|
87048
|
+
existingObjects.push(existingObject);
|
|
87049
|
+
continue;
|
|
87050
|
+
}
|
|
87051
|
+
createObjectsParams.push(filteredObject);
|
|
87052
|
+
}
|
|
87053
|
+
|
|
87054
|
+
// Create metaobjects in the creation list, add them to list of existing metaobjects
|
|
87055
|
+
|
|
87056
|
+
for (let i = 0, len = createObjectsParams.length; i < len; i++) {
|
|
87057
|
+
const createObject = createObjectsParams[i];
|
|
87058
|
+
const type = createObject.type;
|
|
87059
|
+
const objectId = createObject.id;
|
|
87060
|
+
const originalSystemId = createObject.id;
|
|
87061
|
+
const name = createObject.name;
|
|
87062
|
+
const propertySets = [];
|
|
87063
|
+
if (createObject.propertySetIds && createObject.propertySetIds.length > 0) {
|
|
87064
|
+
for (let j = 0, lenj = createObject.propertySetIds.length; j < lenj; j++) {
|
|
87065
|
+
const propertySetId = createObject.propertySetIds[j];
|
|
87066
|
+
const propertySet = metaModel.propertySets[propertySetId];
|
|
87067
|
+
if (propertySet) {
|
|
87068
|
+
propertySets.push(propertySet);
|
|
87069
|
+
}
|
|
87070
|
+
}
|
|
87071
|
+
}
|
|
87072
|
+
const parent = null;
|
|
87073
|
+
const children = null;
|
|
87074
|
+
const external = createObject.external;
|
|
87075
|
+
const metaObject = new MetaObject(metaModel, objectId, originalSystemId, name, type, propertySets, parent, children, external);
|
|
87076
|
+
metaObject._parentId = createObject.parent;
|
|
87077
|
+
this.metaObjects[objectId] = metaObject;
|
|
87078
|
+
(this.metaObjectsByType[type] || (this.metaObjectsByType[type] = {}))[objectId] = metaObject;
|
|
87079
|
+
if (this._typeCounts[type] === undefined) {
|
|
87080
|
+
this._typeCounts[type] = 1;
|
|
87081
|
+
} else {
|
|
87082
|
+
this._typeCounts[type]++;
|
|
87083
|
+
}
|
|
87084
|
+
existingObjects.push(metaObject);
|
|
87085
|
+
if (createObject.parent === undefined || createObject.parent === null) ;
|
|
87086
|
+
}
|
|
87087
|
+
|
|
87088
|
+
// Ensure that metaobjects in the existing metaobject list are all attached to their parents,
|
|
87089
|
+
// or registered as root metaobjects
|
|
87090
|
+
|
|
87091
|
+
for (let i = 0, len = existingObjects.length; i < len; i++) {
|
|
87092
|
+
const existingObject = existingObjects[i];
|
|
87093
|
+
const objectId = existingObject.id;
|
|
87094
|
+
if ((existingObject._parentId === undefined || existingObject._parentId === null) && !existingObject.parent) {
|
|
87095
|
+
metaModel.rootMetaObject = existingObject; // Deprecated, and won't work for federated models
|
|
87096
|
+
metaModel.rootMetaObjects[objectId] = existingObject;
|
|
87097
|
+
this.rootMetaObjects[objectId] = existingObject;
|
|
87098
|
+
} else if (existingObject._parentId) {
|
|
87099
|
+
const parentId = existingObject._parentId;
|
|
87100
|
+
existingObject._parentId = null;
|
|
87101
|
+
let parentMetaObject = this.metaObjects[parentId];
|
|
87102
|
+
if (parentMetaObject) {
|
|
87103
|
+
existingObject.parent = parentMetaObject;
|
|
87104
|
+
parentMetaObject.children = parentMetaObject.children || [];
|
|
87105
|
+
parentMetaObject.children.push(existingObject);
|
|
87106
|
+
}
|
|
87107
|
+
}
|
|
87108
|
+
}
|
|
87109
|
+
|
|
87110
|
+
this.fire("metaModelCreated", modelId);
|
|
87111
|
+
return metaModel;
|
|
87112
|
+
}
|
|
87113
|
+
|
|
87114
|
+
/**
|
|
87115
|
+
* Removes a {@link MetaModel} from this MetaScene.
|
|
87116
|
+
*
|
|
87117
|
+
* Fires a "metaModelDestroyed" event with the value of the {@link MetaModel#id}.
|
|
87118
|
+
*
|
|
87119
|
+
* @param {String} id ID of the target {@link MetaModel}.
|
|
87120
|
+
*/
|
|
87121
|
+
destroyMetaModel(id) {
|
|
87122
|
+
const metaModel = this.metaModels[id];
|
|
87123
|
+
if (!metaModel) {
|
|
87124
|
+
return;
|
|
87125
|
+
}
|
|
87126
|
+
this._removeMetaModel(metaModel);
|
|
87127
|
+
this.fire("metaModelDestroyed", id);
|
|
87128
|
+
}
|
|
87129
|
+
|
|
87130
|
+
_removeMetaModel(metaModel) {
|
|
87131
|
+
const metaObjects = this.metaObjects;
|
|
87132
|
+
const metaObjectsByType = this.metaObjectsByType;
|
|
87133
|
+
let visit = (metaObject) => {
|
|
87134
|
+
delete metaObjects[metaObject.id];
|
|
87135
|
+
const types = metaObjectsByType[metaObject.type];
|
|
87136
|
+
if (types && types[metaObject.id]) {
|
|
87137
|
+
delete types[metaObject.id];
|
|
87138
|
+
if (--this._typeCounts[metaObject.type] === 0) {
|
|
87139
|
+
delete this._typeCounts[metaObject.type];
|
|
87140
|
+
delete metaObjectsByType[metaObject.type];
|
|
87141
|
+
}
|
|
87142
|
+
}
|
|
87143
|
+
const children = metaObject.children;
|
|
87144
|
+
if (children) {
|
|
87145
|
+
for (let i = 0, len = children.length; i < len; i++) {
|
|
87146
|
+
const childMetaObject = children[i];
|
|
87147
|
+
visit(childMetaObject);
|
|
87148
|
+
}
|
|
87149
|
+
}
|
|
87150
|
+
};
|
|
87151
|
+
visit(metaModel.rootMetaObject);
|
|
87152
|
+
for (let propertySetId in metaModel.propertySets) {
|
|
87153
|
+
if (metaModel.propertySets.hasOwnProperty(propertySetId)) {
|
|
87154
|
+
delete this.propertySets[propertySetId];
|
|
87155
|
+
}
|
|
87156
|
+
}
|
|
87157
|
+
delete this.metaModels[metaModel.id];
|
|
87158
|
+
}
|
|
87159
|
+
|
|
87160
|
+
/**
|
|
87161
|
+
* Gets the {@link MetaObject#id}s of the {@link MetaObject}s that have the given {@link MetaObject#type}.
|
|
87162
|
+
*
|
|
87163
|
+
* @param {String} type The type.
|
|
87164
|
+
* @returns {String[]} Array of {@link MetaObject#id}s.
|
|
87165
|
+
*/
|
|
87166
|
+
getObjectIDsByType(type) {
|
|
87167
|
+
const metaObjects = this.metaObjectsByType[type];
|
|
87168
|
+
return metaObjects ? Object.keys(metaObjects) : [];
|
|
87169
|
+
}
|
|
87170
|
+
|
|
87171
|
+
/**
|
|
87172
|
+
* Gets the {@link MetaObject#id}s of the {@link MetaObject}s within the given subtree.
|
|
87173
|
+
*
|
|
87174
|
+
* @param {String} id ID of the root {@link MetaObject} of the given subtree.
|
|
87175
|
+
* @param {String[]} [includeTypes] Optional list of types to include.
|
|
87176
|
+
* @param {String[]} [excludeTypes] Optional list of types to exclude.
|
|
87177
|
+
* @returns {String[]} Array of {@link MetaObject#id}s.
|
|
87178
|
+
*/
|
|
87179
|
+
getObjectIDsInSubtree(id, includeTypes, excludeTypes) {
|
|
87180
|
+
const list = [];
|
|
87181
|
+
const metaObject = this.metaObjects[id];
|
|
87182
|
+
const includeMask = (includeTypes && includeTypes.length > 0) ? arrayToMap(includeTypes) : null;
|
|
87183
|
+
const excludeMask = (excludeTypes && excludeTypes.length > 0) ? arrayToMap(excludeTypes) : null;
|
|
87184
|
+
|
|
87185
|
+
function visit(metaObject) {
|
|
87186
|
+
if (!metaObject) {
|
|
87187
|
+
return;
|
|
87188
|
+
}
|
|
87189
|
+
var include = true;
|
|
87190
|
+
if (excludeMask && excludeMask[metaObject.type]) {
|
|
87191
|
+
include = false;
|
|
87192
|
+
} else if (includeMask && (!includeMask[metaObject.type])) {
|
|
87193
|
+
include = false;
|
|
87194
|
+
}
|
|
87195
|
+
if (include) {
|
|
87196
|
+
list.push(metaObject.id);
|
|
87197
|
+
}
|
|
87198
|
+
const children = metaObject.children;
|
|
87199
|
+
if (children) {
|
|
87200
|
+
for (var i = 0, len = children.length; i < len; i++) {
|
|
87201
|
+
visit(children[i]);
|
|
87202
|
+
}
|
|
87203
|
+
}
|
|
87204
|
+
}
|
|
87205
|
+
|
|
87206
|
+
visit(metaObject);
|
|
87207
|
+
return list;
|
|
87208
|
+
}
|
|
87209
|
+
|
|
87210
|
+
/**
|
|
87211
|
+
* Iterates over the {@link MetaObject}s within the subtree.
|
|
87212
|
+
*
|
|
87213
|
+
* @param {String} id ID of root {@link MetaObject}.
|
|
87214
|
+
* @param {Function} callback Callback fired at each {@link MetaObject}.
|
|
87215
|
+
*/
|
|
87216
|
+
withMetaObjectsInSubtree(id, callback) {
|
|
87217
|
+
const metaObject = this.metaObjects[id];
|
|
87218
|
+
if (!metaObject) {
|
|
87219
|
+
return;
|
|
87220
|
+
}
|
|
87221
|
+
metaObject.withMetaObjectsInSubtree(callback);
|
|
87222
|
+
}
|
|
87223
|
+
}
|
|
87224
|
+
|
|
87225
|
+
function arrayToMap(array) {
|
|
87226
|
+
const map = {};
|
|
87227
|
+
for (var i = 0, len = array.length; i < len; i++) {
|
|
87228
|
+
map[array[i]] = true;
|
|
87229
|
+
}
|
|
87230
|
+
return map;
|
|
87231
|
+
}
|
|
87232
|
+
|
|
87233
|
+
/**
|
|
87234
|
+
* The 3D Viewer at the heart of the xeokit SDK.
|
|
87235
|
+
*
|
|
87236
|
+
* * A Viewer wraps a single {@link Scene}
|
|
87237
|
+
* * Add {@link Plugin}s to a Viewer to extend its functionality.
|
|
87238
|
+
* * {@link Viewer#metaScene} holds metadata about models in the
|
|
87239
|
+
* Viewer's {@link MetaScene}.
|
|
87240
|
+
* * Use {@link Viewer#cameraFlight} to fly or jump the {@link Scene}'s
|
|
87241
|
+
* {@link Camera} to target positions, boundaries or {@link Entity}s.
|
|
87242
|
+
*
|
|
87243
|
+
* @public
|
|
87244
|
+
*/
|
|
87245
|
+
class Viewer {
|
|
87246
|
+
|
|
87247
|
+
/**
|
|
87248
|
+
* @constructor
|
|
87249
|
+
* @param {Object} cfg Viewer configuration.
|
|
87250
|
+
* @param {String} [cfg.id] Optional ID for this Viewer, defaults to the ID of {@link Viewer#scene}, which xeokit automatically generates.
|
|
87251
|
+
* @param {String} [cfg.canvasId] ID of an existing HTML canvas for the {@link Viewer#scene} - either this or canvasElement is mandatory. When both values are given, the element reference is always preferred to the ID.
|
|
87252
|
+
* @param {HTMLCanvasElement} [cfg.canvasElement] Reference of an existing HTML canvas for the {@link Viewer#scene} - either this or canvasId is mandatory. When both values are given, the element reference is always preferred to the ID.
|
|
87253
|
+
* @param {HTMLElement} [cfg.keyboardEventsElement] Optional reference to HTML element on which key events should be handled. Defaults to the HTML Document.
|
|
87254
|
+
* @param {String} [cfg.spinnerElementId] ID of existing HTML element to show the {@link Spinner} - internally creates a default element automatically if this is omitted.
|
|
87255
|
+
* @param {Number} [cfg.passes=1] The number of times the {@link Viewer#scene} renders per frame.
|
|
87256
|
+
* @param {Boolean} [cfg.clearEachPass=false] When doing multiple passes per frame, specifies if to clear the canvas before each pass (true) or just before the first pass (false).
|
|
87257
|
+
* @param {Boolean} [cfg.preserveDrawingBuffer=true] Whether or not to preserve the WebGL drawing buffer. This needs to be ````true```` for {@link Viewer#getSnapshot} to work.
|
|
87258
|
+
* @param {Boolean} [cfg.transparent=true] Whether or not the canvas is transparent.
|
|
87259
|
+
* @param {Boolean} [cfg.premultipliedAlpha=false] Whether or not you want alpha composition with premultiplied alpha. Highlighting and selection works best when this is ````false````.
|
|
87260
|
+
* @param {Boolean} [cfg.gammaInput=true] When true, expects that all textures and colors are premultiplied gamma.
|
|
87261
|
+
* @param {Boolean} [cfg.gammaOutput=false] Whether or not to render with pre-multiplied gama.
|
|
87262
|
+
* @param {Number} [cfg.gammaFactor=2.2] The gamma factor to use when rendering with pre-multiplied gamma.
|
|
87263
|
+
* @param {Number[]} [cfg.backgroundColor=[1,1,1]] Sets the canvas background color to use when ````transparent```` is false.
|
|
87264
|
+
* @param {Boolean} [cfg.backgroundColorFromAmbientLight=true] When ````transparent```` is false, set this ````true````
|
|
87265
|
+
* to derive the canvas background color from {@link AmbientLight#color}, or ````false```` to set the canvas background to ````backgroundColor````.
|
|
87266
|
+
* @param {String} [cfg.units="meters"] The measurement unit type. Accepted values are ````"meters"````, ````"metres"````, , ````"centimeters"````, ````"centimetres"````, ````"millimeters"````, ````"millimetres"````, ````"yards"````, ````"feet"```` and ````"inches"````.
|
|
87267
|
+
* @param {Number} [cfg.scale=1] The number of Real-space units in each World-space coordinate system unit.
|
|
87268
|
+
* @param {Number[]} [cfg.origin=[0,0,0]] The Real-space 3D origin, in current measurement units, at which the World-space coordinate origin ````[0,0,0]```` sits.
|
|
87269
|
+
* @param {Boolean} [cfg.saoEnabled=false] Whether to enable Scalable Ambient Obscurance (SAO) effect. See {@link SAO} for more info.
|
|
87270
|
+
* @param {Boolean} [cfg.antialias=true] Whether to enable anti-aliasing.
|
|
87271
|
+
* @throws {String} Throws an exception when both canvasId or canvasElement are missing or they aren't pointing to a valid HTMLCanvasElement.
|
|
87272
|
+
* @param {Boolean} [cfg.alphaDepthMask=true] Whether writing into the depth buffer is enabled or disabled when rendering transparent objects.
|
|
87273
|
+
* @param {Boolean} [cfg.entityOffsetsEnabled=false] Whether to enable {@link Entity#offset}. For best performance, only set this ````true```` when you need to use {@link Entity#offset}.
|
|
87274
|
+
* @param {Boolean} [cfg.pickSurfacePrecisionEnabled=false] Whether to enable full-precision accuracy when surface picking with {@link Scene#pick}. Note that when ````true````, this configuration will increase the amount of browser memory used by the Viewer. The ````pickSurfacePrecision```` option for ````Scene#pick```` only works if this is set ````true````.
|
|
87275
|
+
* @param {Boolean} [cfg.logarithmicDepthBufferEnabled=false] Whether to enable logarithmic depth buffer. When this is true,
|
|
87276
|
+
* you can set huge values for {@link Perspective#far} and {@link Ortho#far}, to push the far clipping plane back so
|
|
87277
|
+
* that it does not clip huge models.
|
|
87278
|
+
* @param {Boolean} [cfg.colorTextureEnabled=true] Whether to enable base color texture rendering.
|
|
87279
|
+
* @param {Boolean} [cfg.pbrEnabled=false] Whether to enable physically-based rendering.
|
|
87280
|
+
* @param {LocaleService} [cfg.localeService=null] Optional locale-based translation service.
|
|
87281
|
+
*/
|
|
87282
|
+
constructor(cfg) {
|
|
87283
|
+
|
|
87284
|
+
/**
|
|
87285
|
+
* The Viewer's current language setting.
|
|
87286
|
+
* @property language
|
|
87287
|
+
* @deprecated
|
|
87288
|
+
* @type {String}
|
|
87289
|
+
*/
|
|
87290
|
+
this.language = "en";
|
|
87291
|
+
|
|
87292
|
+
/**
|
|
87293
|
+
* The viewer's locale service.
|
|
87294
|
+
*
|
|
87295
|
+
* This is configured via the Viewer's constructor.
|
|
87296
|
+
*
|
|
87297
|
+
* By default, this service will be an instance of {@link LocaleService}, which will just return
|
|
87298
|
+
* null translations for all given strings and phrases.
|
|
87299
|
+
*
|
|
87300
|
+
* @property localeService
|
|
87301
|
+
* @type {LocaleService}
|
|
87302
|
+
* @since 2.0
|
|
87303
|
+
*/
|
|
87304
|
+
this.localeService = cfg.localeService || new LocaleService();
|
|
87305
|
+
|
|
87306
|
+
/**
|
|
87307
|
+
* The Viewer's {@link Scene}.
|
|
87308
|
+
* @property scene
|
|
87309
|
+
* @type {Scene}
|
|
87310
|
+
*/
|
|
87311
|
+
this.scene = new Scene(this, {
|
|
87312
|
+
canvasId: cfg.canvasId,
|
|
87313
|
+
canvasElement: cfg.canvasElement,
|
|
87314
|
+
keyboardEventsElement: cfg.keyboardEventsElement,
|
|
87315
|
+
contextAttr: {
|
|
87316
|
+
preserveDrawingBuffer: cfg.preserveDrawingBuffer !== false,
|
|
87317
|
+
premultipliedAlpha: (!!cfg.premultipliedAlpha),
|
|
87318
|
+
antialias: (cfg.antialias !== false)
|
|
87319
|
+
},
|
|
87320
|
+
spinnerElementId: cfg.spinnerElementId,
|
|
87321
|
+
transparent: (cfg.transparent !== false),
|
|
87322
|
+
gammaInput: true,
|
|
87323
|
+
gammaOutput: false,
|
|
87324
|
+
backgroundColor: cfg.backgroundColor,
|
|
87325
|
+
backgroundColorFromAmbientLight: cfg.backgroundColorFromAmbientLight,
|
|
87326
|
+
ticksPerRender: 1,
|
|
87327
|
+
ticksPerOcclusionTest: 20,
|
|
87328
|
+
units: cfg.units,
|
|
87329
|
+
scale: cfg.scale,
|
|
87330
|
+
origin: cfg.origin,
|
|
87331
|
+
saoEnabled: cfg.saoEnabled,
|
|
87332
|
+
alphaDepthMask: (cfg.alphaDepthMask !== false),
|
|
87333
|
+
entityOffsetsEnabled: (!!cfg.entityOffsetsEnabled),
|
|
87334
|
+
pickSurfacePrecisionEnabled: (!!cfg.pickSurfacePrecisionEnabled),
|
|
87335
|
+
logarithmicDepthBufferEnabled: (!!cfg.logarithmicDepthBufferEnabled),
|
|
87336
|
+
pbrEnabled: (!!cfg.pbrEnabled),
|
|
87337
|
+
colorTextureEnabled: (cfg.colorTextureEnabled !== false)
|
|
87338
|
+
});
|
|
87339
|
+
|
|
87340
|
+
/**
|
|
87341
|
+
* Metadata about the {@link Scene} and the models and objects within it.
|
|
87342
|
+
* @property metaScene
|
|
87343
|
+
* @type {MetaScene}
|
|
87344
|
+
* @readonly
|
|
87345
|
+
*/
|
|
87346
|
+
this.metaScene = new MetaScene(this, this.scene);
|
|
87347
|
+
|
|
87348
|
+
/**
|
|
87349
|
+
* The Viewer's ID.
|
|
87350
|
+
* @property id
|
|
87351
|
+
*
|
|
87352
|
+
* @type {String|Number}
|
|
87353
|
+
*/
|
|
87354
|
+
this.id = cfg.id || this.scene.id;
|
|
87355
|
+
|
|
87356
|
+
/**
|
|
87357
|
+
* The Viewer's {@link Camera}. This is also found on {@link Scene#camera}.
|
|
87358
|
+
* @property camera
|
|
87359
|
+
* @type {Camera}
|
|
87360
|
+
*/
|
|
87361
|
+
this.camera = this.scene.camera;
|
|
87362
|
+
|
|
87363
|
+
/**
|
|
87364
|
+
* The Viewer's {@link CameraFlightAnimation}, which
|
|
87365
|
+
* is used to fly the {@link Scene}'s {@link Camera} to given targets.
|
|
87366
|
+
* @property cameraFlight
|
|
87367
|
+
* @type {CameraFlightAnimation}
|
|
87368
|
+
*/
|
|
87369
|
+
this.cameraFlight = new CameraFlightAnimation(this.scene, {
|
|
87370
|
+
duration: 0.5
|
|
87371
|
+
});
|
|
87372
|
+
|
|
87373
|
+
/**
|
|
87374
|
+
* The Viewer's {@link CameraControl}, which
|
|
87375
|
+
* controls the {@link Scene}'s {@link Camera} with mouse, touch and keyboard input.
|
|
87376
|
+
* @property cameraControl
|
|
87377
|
+
* @type {CameraControl}
|
|
87378
|
+
*/
|
|
87379
|
+
this.cameraControl = new CameraControl(this.scene, {
|
|
87380
|
+
// panToPointer: true,
|
|
87381
|
+
doublePickFlyTo: true
|
|
87382
|
+
});
|
|
87383
|
+
|
|
87384
|
+
this._plugins = [];
|
|
87385
|
+
|
|
87386
|
+
/**
|
|
87387
|
+
* Subscriptions to events sent with {@link fire}.
|
|
87388
|
+
* @private
|
|
87389
|
+
*/
|
|
87390
|
+
this._eventSubs = {};
|
|
87391
|
+
}
|
|
87392
|
+
|
|
87393
|
+
/**
|
|
87394
|
+
* Returns the capabilities of this Viewer.
|
|
87395
|
+
*
|
|
87396
|
+
* @returns {{astcSupported: boolean, etc1Supported: boolean, pvrtcSupported: boolean, etc2Supported: boolean, dxtSupported: boolean, bptcSupported: boolean}}
|
|
87397
|
+
*/
|
|
87398
|
+
get capabilities() {
|
|
87399
|
+
return this.scene.capabilities;
|
|
87400
|
+
}
|
|
87401
|
+
|
|
87381
87402
|
/**
|
|
87382
87403
|
* Subscribes to an event fired at this Viewer.
|
|
87383
87404
|
*
|
|
@@ -103174,142 +103195,398 @@ class StoreyViewsPlugin extends Plugin {
|
|
|
103174
103195
|
}
|
|
103175
103196
|
|
|
103176
103197
|
/**
|
|
103177
|
-
*
|
|
103198
|
+
* @desc A {@link Viewer} plugin that provides an HTML tree view to navigate the IFC elements in models.
|
|
103199
|
+
* <br>
|
|
103178
103200
|
*
|
|
103179
|
-
*
|
|
103180
|
-
* @param {String[]} errors Accumulates messages for validation errors.
|
|
103181
|
-
* @return {boolean} Returns ````true```` if no errors found, else ````false````.
|
|
103182
|
-
*/
|
|
103183
|
-
function validateMetaModelForTreeViewTypesHierarchy(metaModel, errors) {
|
|
103184
|
-
const rootMetaObject = metaModel.rootMetaObject;
|
|
103185
|
-
if (!rootMetaObject) {
|
|
103186
|
-
errors.push("Can't build types hierarchy: model is empty");
|
|
103187
|
-
return false;
|
|
103188
|
-
}
|
|
103189
|
-
return true;
|
|
103190
|
-
}
|
|
103191
|
-
|
|
103192
|
-
/**
|
|
103193
|
-
* Tests if {@link TreeViewPlugin} would be able to create a "storeys" hierarchy for the given {@link MetaModel}.
|
|
103201
|
+
* <a href="https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_WestRiverSideHospital" style="border: 1px solid black;"><img src="http://xeokit.io/img/docs/TreeViewPlugin/TreeViewPlugin.png"></a>
|
|
103194
103202
|
*
|
|
103195
|
-
*
|
|
103196
|
-
* @param {String[]} errors Accumulates messages for validation errors.
|
|
103197
|
-
* @return {boolean} Returns ````true```` if no errors found, else ````false````.
|
|
103198
|
-
*/
|
|
103199
|
-
function validateMetaModelForTreeViewStoreysHierarchy(metaModel, errors) {
|
|
103200
|
-
const rootMetaObject = metaModel.rootMetaObject;
|
|
103201
|
-
if (!rootMetaObject) {
|
|
103202
|
-
errors.push("Can't build storeys hierarchy: model is empty");
|
|
103203
|
-
return false;
|
|
103204
|
-
}
|
|
103205
|
-
return _validateMetaModelForStoreysHierarchy(rootMetaObject, errors);
|
|
103206
|
-
}
|
|
103207
|
-
|
|
103208
|
-
/**
|
|
103209
|
-
* Tests if {@link TreeViewPlugin} would be able to create a "containment" hierarchy for the given {@link MetaModel}.
|
|
103203
|
+
* [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_WestRiverSideHospital)]
|
|
103210
103204
|
*
|
|
103211
|
-
*
|
|
103212
|
-
*
|
|
103213
|
-
*
|
|
103214
|
-
|
|
103215
|
-
|
|
103216
|
-
|
|
103217
|
-
|
|
103218
|
-
|
|
103219
|
-
|
|
103220
|
-
|
|
103221
|
-
|
|
103222
|
-
|
|
103223
|
-
|
|
103224
|
-
|
|
103225
|
-
*
|
|
103226
|
-
|
|
103227
|
-
|
|
103228
|
-
|
|
103229
|
-
|
|
103230
|
-
|
|
103231
|
-
|
|
103232
|
-
|
|
103233
|
-
|
|
103234
|
-
|
|
103235
|
-
|
|
103236
|
-
|
|
103237
|
-
|
|
103238
|
-
|
|
103239
|
-
|
|
103240
|
-
|
|
103241
|
-
|
|
103242
|
-
|
|
103243
|
-
|
|
103244
|
-
|
|
103245
|
-
|
|
103246
|
-
|
|
103247
|
-
|
|
103248
|
-
|
|
103249
|
-
|
|
103250
|
-
|
|
103251
|
-
|
|
103252
|
-
|
|
103253
|
-
|
|
103254
|
-
|
|
103255
|
-
|
|
103256
|
-
|
|
103257
|
-
|
|
103258
|
-
|
|
103259
|
-
*
|
|
103205
|
+
* ## Overview
|
|
103206
|
+
*
|
|
103207
|
+
* * A fast HTML tree view, with zero external dependencies, that works with huge numbers of objects.
|
|
103208
|
+
* * Each tree node has a checkbox to control the visibility of its object.
|
|
103209
|
+
* * Has three hierarchy modes: "containment", "types" and "storeys".
|
|
103210
|
+
* * Automatically contains all models (that have metadata) that are currently in the {@link Scene}.
|
|
103211
|
+
* * Sorts tree nodes by default - spatially, from top-to-bottom for ````IfcBuildingStorey```` nodes, and alphanumerically for other nodes.
|
|
103212
|
+
* * Allows custom CSS styling.
|
|
103213
|
+
* * Use {@link ContextMenu} to create a context menu for the tree nodes.
|
|
103214
|
+
*
|
|
103215
|
+
* ## Credits
|
|
103216
|
+
*
|
|
103217
|
+
* TreeViewPlugin is based on techniques described in [*Super Fast Tree View in JavaScript*](https://chrissmith.xyz/super-fast-tree-view-in-javascript/) by [Chris Smith](https://twitter.com/chris22smith).
|
|
103218
|
+
*
|
|
103219
|
+
* ## Usage
|
|
103220
|
+
*
|
|
103221
|
+
* In the example below, we'll add a TreeViewPlugin which, by default, will automatically show the structural
|
|
103222
|
+
* hierarchy of the IFC elements in each model we load.
|
|
103223
|
+
*
|
|
103224
|
+
* Then we'll use an {@link XKTLoaderPlugin} to load the Schependomlaan model from an
|
|
103225
|
+
* [.xkt file](https://github.com/xeokit/xeokit-sdk/tree/master/examples/models/xkt/schependomlaan).
|
|
103226
|
+
*
|
|
103227
|
+
* [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_Schependomlaan)]
|
|
103228
|
+
*
|
|
103229
|
+
* ````javascript
|
|
103230
|
+
* import {Viewer, XKTLoaderPlugin, TreeViewPlugin} from "xeokit-sdk.es.js";
|
|
103231
|
+
*
|
|
103232
|
+
* const viewer = new Viewer({
|
|
103233
|
+
* canvasId: "myCanvas",
|
|
103234
|
+
* transparent: true
|
|
103235
|
+
* });
|
|
103236
|
+
*
|
|
103237
|
+
* viewer.camera.eye = [-2.56, 8.38, 8.27];
|
|
103238
|
+
* viewer.camera.look = [13.44, 3.31, -14.83];
|
|
103239
|
+
* viewer.camera.up = [0.10, 0.98, -0.14];
|
|
103240
|
+
*
|
|
103241
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103242
|
+
* containerElement: document.getElementById("myTreeViewContainer")
|
|
103243
|
+
* });
|
|
103244
|
+
*
|
|
103245
|
+
* const xktLoader = new XKTLoaderPlugin(viewer);
|
|
103246
|
+
*
|
|
103247
|
+
* const model = xktLoader.load({
|
|
103248
|
+
* id: "myModel",
|
|
103249
|
+
* src: "./models/xkt/Schependomlaan.xkt",
|
|
103250
|
+
* edges: true
|
|
103251
|
+
* });
|
|
103252
|
+
* ````
|
|
103253
|
+
*
|
|
103254
|
+
* ## Manually Adding Models
|
|
103255
|
+
*
|
|
103256
|
+
* Instead of adding models automatically, we can control which models appear in our TreeViewPlugin by adding them manually.
|
|
103257
|
+
*
|
|
103258
|
+
* In the next example, we'll configure the TreeViewPlugin to not add models automatically. Then, once the model
|
|
103259
|
+
* has loaded, we'll add it manually using {@link TreeViewPlugin#addModel}.
|
|
103260
|
+
*
|
|
103261
|
+
* ````javascript
|
|
103262
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103263
|
+
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
103264
|
+
* autoAddModels: false // <<---------------- Don't auto-add models
|
|
103265
|
+
* });
|
|
103266
|
+
*
|
|
103267
|
+
* const xktLoader = new XKTLoaderPlugin(viewer);
|
|
103268
|
+
*
|
|
103269
|
+
* const model = xktLoader.load({
|
|
103270
|
+
* id: "myModel",
|
|
103271
|
+
* src: "./models/xkt/Schependomlaan.xkt",
|
|
103272
|
+
* edges: true
|
|
103273
|
+
* });
|
|
103274
|
+
*
|
|
103275
|
+
* model.on("loaded", () => {
|
|
103276
|
+
* treeView.addModel(model.id);
|
|
103277
|
+
* });
|
|
103278
|
+
* ````
|
|
103279
|
+
*
|
|
103280
|
+
* Adding models manually also allows us to set some options for the model. For example, the ````rootName```` option allows us to provide a custom name for
|
|
103281
|
+
* the root node, which is sometimes desirable when the model's "IfcProject" element's name is not suitable:
|
|
103282
|
+
*
|
|
103283
|
+
* ````javascript
|
|
103284
|
+
* model.on("loaded", () => {
|
|
103285
|
+
* treeView.addModel(model.id, {
|
|
103286
|
+
* rootName: "Schependomlaan Model"
|
|
103287
|
+
* });
|
|
103288
|
+
* });
|
|
103289
|
+
* ````
|
|
103290
|
+
*
|
|
103291
|
+
* ## Initially Expanding the Hierarchy
|
|
103292
|
+
*
|
|
103293
|
+
* We can also configure TreeViewPlugin to initially expand each model's nodes to a given depth.
|
|
103294
|
+
*
|
|
103295
|
+
* Let's automatically expand the first three nodes from the root, for every model added:
|
|
103296
|
+
*
|
|
103297
|
+
* ````javascript
|
|
103298
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103299
|
+
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
103300
|
+
* autoExpandDepth: 3
|
|
103301
|
+
* });
|
|
103302
|
+
* ````
|
|
103303
|
+
*
|
|
103304
|
+
* ## Showing a Node by ID
|
|
103305
|
+
*
|
|
103306
|
+
* We can show a given node using its ID. This causes the TreeViewPlugin to collapse, then expand and scroll the node into view, then highlight the node.
|
|
103307
|
+
*
|
|
103308
|
+
* See the documentation for the {@link TreeViewPlugin#showNode} method for more information, including how to define a custom highlighted appearance for the node using CSS.
|
|
103309
|
+
*
|
|
103310
|
+
* Let's make the TreeViewPlugin show the node corresponding to whatever object {@link Entity} that we pick:
|
|
103311
|
+
*
|
|
103312
|
+
* ````javascript
|
|
103313
|
+
* viewer.cameraControl.on("picked", function (e) {
|
|
103314
|
+
* var objectId = e.entity.id;
|
|
103315
|
+
* treeView.showNode(objectId);
|
|
103316
|
+
* });
|
|
103317
|
+
* ````
|
|
103318
|
+
*
|
|
103319
|
+
* This will de-highlight any node that was previously shown by this method.
|
|
103320
|
+
*
|
|
103321
|
+
* Note that this method only works if the picked {@link Entity} is an object that belongs to a model that's represented in the TreeViewPlugin.
|
|
103322
|
+
*
|
|
103323
|
+
* ## Customizing Appearance
|
|
103324
|
+
*
|
|
103325
|
+
* We can customize the appearance of our TreeViewPlugin by defining custom CSS for its HTML
|
|
103326
|
+
* elements. See our example's [source code](https://github.com/xeokit/xeokit-sdk/blob/master/examples/BIMOffline_XKT_Schependomlaan.html)
|
|
103327
|
+
* for an example of custom CSS rules.
|
|
103328
|
+
*
|
|
103329
|
+
* ## Model Hierarchies
|
|
103330
|
+
*
|
|
103331
|
+
* TreeViewPlugin has three hierarchies for organizing its nodes:
|
|
103332
|
+
*
|
|
103333
|
+
* * "containment" - organizes the tree nodes to indicate the containment hierarchy of the {@link MetaObject}s.
|
|
103334
|
+
* * "types" - groups nodes by their IFC types.
|
|
103335
|
+
* * "storeys" - groups nodes within their ````IfcBuildingStoreys````, and sub-groups them by their IFC types.
|
|
103336
|
+
*
|
|
103337
|
+
* <br>
|
|
103338
|
+
* The table below shows what the hierarchies look like:
|
|
103339
|
+
* <br>
|
|
103340
|
+
*
|
|
103341
|
+
* | 1. Containment Hierarchy | 2. Types Hierarchy | 3. Storeys Hierarchy |
|
|
103342
|
+
* |---|---|---|
|
|
103343
|
+
* | <img src="http://xeokit.io/img/docs/TreeViewPlugin/structureMode.png"> | <img src="http://xeokit.io/img/docs/TreeViewPlugin/typesMode.png"> | <img src="http://xeokit.io/img/docs/TreeViewPlugin/storeysMode.png"> |
|
|
103344
|
+
* <br>
|
|
103345
|
+
*
|
|
103346
|
+
* Let's create a TreeViewPlugin that groups nodes by their building stories and IFC types:
|
|
103347
|
+
*
|
|
103348
|
+
* ````javascript
|
|
103349
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103350
|
+
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
103351
|
+
* hierarchy: "stories"
|
|
103352
|
+
* });
|
|
103353
|
+
* ````
|
|
103354
|
+
*
|
|
103355
|
+
* ## Sorting Nodes
|
|
103356
|
+
*
|
|
103357
|
+
* TreeViewPlugin sorts its tree nodes by default. For a "storeys" hierarchy, it orders ````IfcBuildingStorey```` nodes
|
|
103358
|
+
* spatially, with the node for the highest story at the top, down to the lowest at the bottom.
|
|
103359
|
+
*
|
|
103360
|
+
* For all the hierarchy types ("containment", "classes" and "storeys"), TreeViewPlugin sorts the other node types
|
|
103361
|
+
* alphanumerically on their titles.
|
|
103362
|
+
*
|
|
103363
|
+
* If for some reason you need to prevent sorting, create your TreeViewPlugin with the option disabled, like so:
|
|
103364
|
+
*
|
|
103365
|
+
* ````javascript
|
|
103366
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103367
|
+
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
103368
|
+
* hierarchy: "stories",
|
|
103369
|
+
* sortNodes: false // <<------ Disable node sorting
|
|
103370
|
+
* });
|
|
103371
|
+
* ````
|
|
103372
|
+
*
|
|
103373
|
+
* Note that, for all hierarchy modes, node sorting is only done for each model at the time that it is added to the TreeViewPlugin, and will not
|
|
103374
|
+
* update dynamically if we later transform the {@link Entity}s corresponding to the nodes.
|
|
103375
|
+
*
|
|
103376
|
+
* ## Pruning empty nodes
|
|
103377
|
+
*
|
|
103378
|
+
* Sometimes a model contains subtrees of objects that don't have any geometry. These are models whose
|
|
103379
|
+
* {@link MetaModel} contains trees of {@link MetaObject}s that don't have any {@link Entity}s in the {@link Scene}.
|
|
103380
|
+
*
|
|
103381
|
+
* For these models, the tree view would contain nodes that don't do anything in the Scene when we interact with them,
|
|
103382
|
+
* which is undesirable.
|
|
103383
|
+
*
|
|
103384
|
+
* By default, TreeViewPlugin will not create nodes for those objects. However, we can override that behaviour if we want
|
|
103385
|
+
* to have nodes for those objects (perhaps for debugging the model):
|
|
103386
|
+
*
|
|
103387
|
+
* ````javascript
|
|
103388
|
+
* const treeView = new TreeViewPlugin(viewer, {
|
|
103389
|
+
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
103390
|
+
* hierarchy: "stories",
|
|
103391
|
+
* pruneEmptyNodes: false // <<------ Create nodes for object subtrees without geometry
|
|
103392
|
+
* });
|
|
103393
|
+
* ````
|
|
103394
|
+
*
|
|
103395
|
+
* ## Context Menu
|
|
103396
|
+
*
|
|
103397
|
+
* TreeViewPlugin fires a "contextmenu" event whenever we right-click on a tree node.
|
|
103398
|
+
*
|
|
103399
|
+
* The event contains:
|
|
103400
|
+
*
|
|
103401
|
+
* * ````event```` - the original [contextmenu](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event) [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
|
|
103402
|
+
* * ````viewer```` - the {@link Viewer}
|
|
103403
|
+
* * ````treeViewPlugin```` - the TreeViewPlugin
|
|
103404
|
+
* * ````treeViewNode```` - the {@link TreeViewNode} representing the tree node
|
|
103405
|
+
*<br><br>
|
|
103406
|
+
*
|
|
103407
|
+
* Let's use {@link ContextMenu} to show a simple context menu for the node we clicked.
|
|
103408
|
+
*
|
|
103409
|
+
* [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/#ContextMenu_Canvas_TreeViewPlugin_Custom)]
|
|
103410
|
+
*
|
|
103411
|
+
* ````javascript
|
|
103412
|
+
* import {ContextMenu} from "../src/extras/ContextMenu/ContextMenu.js";
|
|
103413
|
+
*
|
|
103414
|
+
* const treeViewContextMenu = new ContextMenu({
|
|
103415
|
+
* items: [
|
|
103416
|
+
* [
|
|
103417
|
+
* [
|
|
103418
|
+
* {
|
|
103419
|
+
* title: "Hide",
|
|
103420
|
+
* doAction: function (context) {
|
|
103421
|
+
* context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
|
|
103422
|
+
* if (treeViewNode.objectId) {
|
|
103423
|
+
* const entity = context.viewer.scene.objects[treeViewNode.objectId];
|
|
103424
|
+
* if (entity) {
|
|
103425
|
+
* entity.visible = false;
|
|
103426
|
+
* }
|
|
103427
|
+
* }
|
|
103428
|
+
* });
|
|
103429
|
+
* }
|
|
103430
|
+
* },
|
|
103431
|
+
* {
|
|
103432
|
+
* title: "Hide all",
|
|
103433
|
+
* doAction: function (context) {
|
|
103434
|
+
* context.viewer.scene.setObjectsVisible(context.viewer.scene.visibleObjectIds, false);
|
|
103435
|
+
* }
|
|
103436
|
+
* }
|
|
103437
|
+
* ],
|
|
103438
|
+
* [
|
|
103439
|
+
* {
|
|
103440
|
+
* title: "Show",
|
|
103441
|
+
* doAction: function (context) {
|
|
103442
|
+
* context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
|
|
103443
|
+
* if (treeViewNode.objectId) {
|
|
103444
|
+
* const entity = context.viewer.scene.objects[treeViewNode.objectId];
|
|
103445
|
+
* if (entity) {
|
|
103446
|
+
* entity.visible = true;
|
|
103447
|
+
* entity.xrayed = false;
|
|
103448
|
+
* entity.selected = false;
|
|
103449
|
+
* }
|
|
103450
|
+
* }
|
|
103451
|
+
* });
|
|
103452
|
+
* }
|
|
103453
|
+
* },
|
|
103454
|
+
* {
|
|
103455
|
+
* title: "Show all",
|
|
103456
|
+
* doAction: function (context) {
|
|
103457
|
+
* const scene = context.viewer.scene;
|
|
103458
|
+
* scene.setObjectsVisible(scene.objectIds, true);
|
|
103459
|
+
* scene.setObjectsXRayed(scene.xrayedObjectIds, false);
|
|
103460
|
+
* scene.setObjectsSelected(scene.selectedObjectIds, false);
|
|
103461
|
+
* }
|
|
103462
|
+
* }
|
|
103463
|
+
* ]
|
|
103464
|
+
* ]
|
|
103465
|
+
* ]
|
|
103466
|
+
* });
|
|
103467
|
+
*
|
|
103468
|
+
* treeView.on("contextmenu", (e) => {
|
|
103469
|
+
*
|
|
103470
|
+
* const event = e.event; // MouseEvent
|
|
103471
|
+
* const viewer = e.viewer; // Viewer
|
|
103472
|
+
* const treeViewPlugin = e.treeViewPlugin; // TreeViewPlugin
|
|
103473
|
+
* const treeViewNode = e.treeViewNode; // TreeViewNode
|
|
103474
|
+
*
|
|
103475
|
+
* treeViewContextMenu.show(e.event.pageX, e.event.pageY);
|
|
103476
|
+
*
|
|
103477
|
+
* treeViewContextMenu.context = {
|
|
103478
|
+
* viewer: e.viewer,
|
|
103479
|
+
* treeViewPlugin: e.treeViewPlugin,
|
|
103480
|
+
* treeViewNode: e.treeViewNode
|
|
103481
|
+
* };
|
|
103482
|
+
* });
|
|
103483
|
+
* ````
|
|
103484
|
+
*
|
|
103485
|
+
* ## Clicking Node Titles
|
|
103486
|
+
*
|
|
103487
|
+
* TreeViewPlugin fires a "nodeTitleClicked" event whenever we left-click on a tree node.
|
|
103488
|
+
*
|
|
103489
|
+
* Like the "contextmenu" event, this event contains:
|
|
103490
|
+
*
|
|
103491
|
+
* * ````event```` - the original [click](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event) [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
|
|
103492
|
+
* * ````viewer```` - the {@link Viewer}
|
|
103493
|
+
* * ````treeViewPlugin```` - the TreeViewPlugin
|
|
103494
|
+
* * ````treeViewNode```` - the {@link TreeViewNode} representing the tree node
|
|
103495
|
+
*<br><br>
|
|
103496
|
+
*
|
|
103497
|
+
* Let's register a callback to isolate and fit-to-view the {@link Entity}(s) represented by the node. This callback is
|
|
103498
|
+
* going to X-ray all the other Entitys, fly the camera to fit the Entity(s) for the clicked node, then hide the other Entitys.
|
|
103499
|
+
*
|
|
103500
|
+
* [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/#ContextMenu_Canvas_TreeViewPlugin_Custom)]
|
|
103501
|
+
*
|
|
103502
|
+
* ````javascript
|
|
103503
|
+
* treeView.on("nodeTitleClicked", (e) => {
|
|
103504
|
+
* const scene = viewer.scene;
|
|
103505
|
+
* const objectIds = [];
|
|
103506
|
+
* e.treeViewPlugin.withNodeTree(e.treeViewNode, (treeViewNode) => {
|
|
103507
|
+
* if (treeViewNode.objectId) {
|
|
103508
|
+
* objectIds.push(treeViewNode.objectId);
|
|
103509
|
+
* }
|
|
103510
|
+
* });
|
|
103511
|
+
* scene.setObjectsXRayed(scene.objectIds, true);
|
|
103512
|
+
* scene.setObjectsVisible(scene.objectIds, true);
|
|
103513
|
+
* scene.setObjectsXRayed(objectIds, false);
|
|
103514
|
+
* viewer.cameraFlight.flyTo({
|
|
103515
|
+
* aabb: scene.getAABB(objectIds),
|
|
103516
|
+
* duration: 0.5
|
|
103517
|
+
* }, () => {
|
|
103518
|
+
* setTimeout(function () {
|
|
103519
|
+
* scene.setObjectsVisible(scene.xrayedObjectIds, false);
|
|
103520
|
+
* scene.setObjectsXRayed(scene.xrayedObjectIds, false);
|
|
103521
|
+
* }, 500);
|
|
103522
|
+
* });
|
|
103523
|
+
* });
|
|
103524
|
+
* ````
|
|
103525
|
+
*
|
|
103526
|
+
* To make the cursor change to a pointer when we hover over the node titles, and also to make the titles change to blue, we'll also define this CSS for the ````<span>```` elements
|
|
103527
|
+
* that represent the titles of our TreeViewPlugin nodes:
|
|
103528
|
+
*
|
|
103529
|
+
* ````css
|
|
103530
|
+
* #treeViewContainer ul li span:hover {
|
|
103531
|
+
* color: blue;
|
|
103532
|
+
* cursor: pointer;
|
|
103533
|
+
* }
|
|
103534
|
+
* ````
|
|
103260
103535
|
*
|
|
103261
|
-
*
|
|
103262
|
-
* * Created by each call to {@link TreeViewPlugin#addModel}.
|
|
103536
|
+
* @class TreeViewPlugin
|
|
103263
103537
|
*/
|
|
103264
|
-
class
|
|
103538
|
+
class TreeViewPlugin extends Plugin {
|
|
103265
103539
|
|
|
103266
103540
|
/**
|
|
103267
|
-
* @
|
|
103541
|
+
* @constructor
|
|
103542
|
+
*
|
|
103543
|
+
* @param {Viewer} viewer The Viewer.
|
|
103544
|
+
* @param {*} cfg Plugin configuration.
|
|
103545
|
+
* @param {HTMLElement} cfg.containerElement DOM element to contain the TreeViewPlugin.
|
|
103546
|
+
* @param {Boolean} [cfg.autoAddModels=true] When ````true```` (default), will automatically add each model as it's created. Set this ````false```` if you want to manually add models using {@link TreeViewPlugin#addModel} instead.
|
|
103547
|
+
* @param {Number} [cfg.autoExpandDepth] Optional depth to which to initially expand the tree.
|
|
103548
|
+
* @param {String} [cfg.hierarchy="containment"] How to organize the tree nodes: "containment", "storeys" or "types". See the class documentation for details.
|
|
103549
|
+
* @param {Boolean} [cfg.sortNodes=true] When true, will sort the children of each node. For a "storeys" hierarchy, the
|
|
103550
|
+
* ````IfcBuildingStorey```` nodes will be ordered spatially, from the highest storey down to the lowest, on the
|
|
103551
|
+
* vertical World axis. For all hierarchy types, other node types will be ordered in the ascending alphanumeric order of their titles.
|
|
103552
|
+
* @param {Boolean} [cfg.pruneEmptyNodes=true] When true, will not contain nodes that don't have content in the {@link Scene}. These are nodes whose {@link MetaObject}s don't have {@link Entity}s.
|
|
103268
103553
|
*/
|
|
103269
|
-
constructor(viewer,
|
|
103270
|
-
|
|
103271
|
-
if (!cfg.containerElement) {
|
|
103272
|
-
throw "Config expected: containerElement";
|
|
103273
|
-
}
|
|
103554
|
+
constructor(viewer, cfg = {}) {
|
|
103274
103555
|
|
|
103275
|
-
|
|
103276
|
-
if (!rootMetaObject) {
|
|
103277
|
-
return;
|
|
103278
|
-
}
|
|
103556
|
+
super("TreeViewPlugin", viewer);
|
|
103279
103557
|
|
|
103280
103558
|
/**
|
|
103281
|
-
* Contains messages for any errors found
|
|
103559
|
+
* Contains messages for any errors found while last rebuilding this TreeView.
|
|
103282
103560
|
* @type {String[]}
|
|
103283
103561
|
*/
|
|
103284
103562
|
this.errors = [];
|
|
103285
103563
|
|
|
103286
103564
|
/**
|
|
103287
|
-
* True if errors were found
|
|
103565
|
+
* True if errors were found generating this TreeView.
|
|
103288
103566
|
* @type {boolean}
|
|
103289
103567
|
*/
|
|
103290
103568
|
this.valid = true;
|
|
103291
103569
|
|
|
103292
|
-
|
|
103293
|
-
|
|
103294
|
-
|
|
103295
|
-
|
|
103296
|
-
this.metaModel = metaModel;
|
|
103570
|
+
if (!cfg.containerElement) {
|
|
103571
|
+
this.error("Config expected: containerElement");
|
|
103572
|
+
return;
|
|
103573
|
+
}
|
|
103297
103574
|
|
|
103298
|
-
this._id = idMap.addItem();
|
|
103299
|
-
this._baseId = "" + this._id;
|
|
103300
|
-
this._viewer = viewer;
|
|
103301
|
-
this._treeViewPlugin = treeViewPlugin;
|
|
103302
|
-
this._rootMetaObject = rootMetaObject;
|
|
103303
103575
|
this._containerElement = cfg.containerElement;
|
|
103576
|
+
this._metaModels = {};
|
|
103577
|
+
this._autoAddModels = (cfg.autoAddModels !== false);
|
|
103578
|
+
this._autoExpandDepth = (cfg.autoExpandDepth || 0);
|
|
103579
|
+
this._sortNodes = (cfg.sortNodes !== false);
|
|
103580
|
+
this._pruneEmptyNodes = (cfg.pruneEmptyNodes !== false);
|
|
103581
|
+
this._viewer = viewer;
|
|
103304
103582
|
this._rootElement = null;
|
|
103305
103583
|
this._muteSceneEvents = false;
|
|
103306
103584
|
this._muteTreeEvents = false;
|
|
103307
103585
|
this._rootNodes = [];
|
|
103308
|
-
this._objectNodes = {};
|
|
103586
|
+
this._objectNodes = {}; // Object ID -> Node
|
|
103309
103587
|
this._rootName = cfg.rootName;
|
|
103310
103588
|
this._sortNodes = cfg.sortNodes;
|
|
103311
103589
|
this._pruneEmptyNodes = cfg.pruneEmptyNodes;
|
|
103312
|
-
|
|
103313
103590
|
this._showListItemElementId = null;
|
|
103314
103591
|
|
|
103315
103592
|
this._containerElement.oncontextmenu = (e) => {
|
|
@@ -103362,43 +103639,41 @@ class ModelTreeView {
|
|
|
103362
103639
|
});
|
|
103363
103640
|
|
|
103364
103641
|
this._onObjectXrayed = this._viewer.scene.on('objectXRayed', (entity) => {
|
|
103365
|
-
|
|
103366
|
-
|
|
103367
|
-
}
|
|
103368
|
-
const objectId = entity.id;
|
|
103369
|
-
const node = this._objectNodes[objectId];
|
|
103370
|
-
if (!node) {
|
|
103371
|
-
return; // Not in this tree
|
|
103372
|
-
}
|
|
103373
|
-
|
|
103374
|
-
this._muteTreeEvents = true;
|
|
103375
|
-
const xrayed = entity.xrayed;
|
|
103376
|
-
const updated = (xrayed !== node.xrayed);
|
|
103377
|
-
if (!updated) {
|
|
103378
|
-
return;
|
|
103379
|
-
}
|
|
103380
|
-
node.xrayed = xrayed;
|
|
103381
|
-
const listItemElementId = 'node-' + node.nodeId;
|
|
103382
|
-
const listItemElement = document.getElementById(listItemElementId);
|
|
103383
|
-
if (listItemElement !== null) {
|
|
103384
|
-
if (xrayed) {
|
|
103385
|
-
listItemElement.classList.add('xrayed-node');
|
|
103386
|
-
} else {
|
|
103387
|
-
listItemElement.classList.remove('xrayed-node');
|
|
103642
|
+
if (this._muteSceneEvents) {
|
|
103643
|
+
return;
|
|
103388
103644
|
}
|
|
103389
|
-
|
|
103390
|
-
|
|
103391
|
-
|
|
103645
|
+
const objectId = entity.id;
|
|
103646
|
+
const node = this._objectNodes[objectId];
|
|
103647
|
+
if (!node) {
|
|
103648
|
+
return; // Not in this tree
|
|
103649
|
+
}
|
|
103650
|
+
this._muteTreeEvents = true;
|
|
103651
|
+
const xrayed = entity.xrayed;
|
|
103652
|
+
const updated = (xrayed !== node.xrayed);
|
|
103653
|
+
if (!updated) {
|
|
103654
|
+
return;
|
|
103655
|
+
}
|
|
103656
|
+
node.xrayed = xrayed;
|
|
103657
|
+
const listItemElementId = 'node-' + node.nodeId;
|
|
103658
|
+
const listItemElement = document.getElementById(listItemElementId);
|
|
103659
|
+
if (listItemElement !== null) {
|
|
103660
|
+
if (xrayed) {
|
|
103661
|
+
listItemElement.classList.add('xrayed-node');
|
|
103662
|
+
} else {
|
|
103663
|
+
listItemElement.classList.remove('xrayed-node');
|
|
103664
|
+
}
|
|
103665
|
+
}
|
|
103666
|
+
this._muteTreeEvents = false;
|
|
103392
103667
|
});
|
|
103393
103668
|
|
|
103394
|
-
this.
|
|
103669
|
+
this._switchExpandHandler = (event) => {
|
|
103395
103670
|
event.preventDefault();
|
|
103396
103671
|
event.stopPropagation();
|
|
103397
103672
|
const switchElement = event.target;
|
|
103398
103673
|
this._expandSwitchElement(switchElement);
|
|
103399
103674
|
};
|
|
103400
103675
|
|
|
103401
|
-
this.
|
|
103676
|
+
this._switchCollapseHandler = (event) => {
|
|
103402
103677
|
event.preventDefault();
|
|
103403
103678
|
event.stopPropagation();
|
|
103404
103679
|
const switchElement = event.target;
|
|
@@ -103413,7 +103688,7 @@ class ModelTreeView {
|
|
|
103413
103688
|
const checkbox = event.target;
|
|
103414
103689
|
const visible = checkbox.checked;
|
|
103415
103690
|
const nodeId = checkbox.id;
|
|
103416
|
-
const checkedObjectId =
|
|
103691
|
+
const checkedObjectId = nodeId;
|
|
103417
103692
|
const checkedNode = this._objectNodes[checkedObjectId];
|
|
103418
103693
|
const objects = this._viewer.scene.objects;
|
|
103419
103694
|
let numUpdated = 0;
|
|
@@ -103456,30 +103731,44 @@ class ModelTreeView {
|
|
|
103456
103731
|
this._hierarchy = cfg.hierarchy || "containment";
|
|
103457
103732
|
this._autoExpandDepth = cfg.autoExpandDepth || 0;
|
|
103458
103733
|
|
|
103459
|
-
this.
|
|
103460
|
-
|
|
103461
|
-
|
|
103462
|
-
|
|
103463
|
-
|
|
103464
|
-
|
|
103465
|
-
|
|
103466
|
-
|
|
103467
|
-
|
|
103468
|
-
|
|
103734
|
+
if (this._autoAddModels) {
|
|
103735
|
+
const modelIds = Object.keys(this.viewer.metaScene.metaModels);
|
|
103736
|
+
for (let i = 0, len = modelIds.length; i < len; i++) {
|
|
103737
|
+
const modelId = modelIds[i];
|
|
103738
|
+
this.addModel(modelId);
|
|
103739
|
+
}
|
|
103740
|
+
this.viewer.scene.on("modelLoaded", (modelId) => {
|
|
103741
|
+
if (this.viewer.metaScene.metaModels[modelId]) {
|
|
103742
|
+
this.addModel(modelId);
|
|
103743
|
+
}
|
|
103744
|
+
});
|
|
103745
|
+
}
|
|
103469
103746
|
|
|
103470
|
-
|
|
103471
|
-
* @private
|
|
103472
|
-
* @param depth
|
|
103473
|
-
*/
|
|
103474
|
-
setAutoExpandDepth(depth = 0) {
|
|
103475
|
-
this._autoExpandDepth = depth;
|
|
103747
|
+
this.hierarchy = cfg.hierarchy;
|
|
103476
103748
|
}
|
|
103477
103749
|
|
|
103478
103750
|
/**
|
|
103479
|
-
*
|
|
103480
|
-
*
|
|
103751
|
+
* Sets how the nodes are organized within this tree view.
|
|
103752
|
+
*
|
|
103753
|
+
* Accepted values are:
|
|
103754
|
+
*
|
|
103755
|
+
* * "containment" - organizes the nodes to indicate the containment hierarchy of the IFC objects.
|
|
103756
|
+
* * "types" - groups the nodes within their IFC types.
|
|
103757
|
+
* * "storeys" - groups the nodes within ````IfcBuildingStoreys```` and sub-groups them by their IFC types.
|
|
103758
|
+
*
|
|
103759
|
+
* <br>
|
|
103760
|
+
* This can be updated dynamically.
|
|
103761
|
+
*
|
|
103762
|
+
* Default value is "containment".
|
|
103763
|
+
*
|
|
103764
|
+
* @type {String}
|
|
103481
103765
|
*/
|
|
103482
|
-
|
|
103766
|
+
set hierarchy(hierarchy) {
|
|
103767
|
+
hierarchy = hierarchy || "containment";
|
|
103768
|
+
if (hierarchy !== "containment" && hierarchy !== "storeys" && hierarchy !== "types") {
|
|
103769
|
+
this.error("Unsupported value for `hierarchy' - defaulting to 'containment'");
|
|
103770
|
+
hierarchy = "containment";
|
|
103771
|
+
}
|
|
103483
103772
|
if (this._hierarchy === hierarchy) {
|
|
103484
103773
|
return;
|
|
103485
103774
|
}
|
|
@@ -103487,38 +103776,287 @@ class ModelTreeView {
|
|
|
103487
103776
|
this._createNodes();
|
|
103488
103777
|
}
|
|
103489
103778
|
|
|
103490
|
-
|
|
103491
|
-
|
|
103492
|
-
|
|
103493
|
-
|
|
103494
|
-
|
|
103495
|
-
|
|
103496
|
-
this.
|
|
103497
|
-
this._validate();
|
|
103498
|
-
if (this.valid || (this._hierarchy !== "storeys")) {
|
|
103499
|
-
this._createEnabledNodes();
|
|
103500
|
-
} else {
|
|
103501
|
-
this._createDisabledNodes();
|
|
103502
|
-
}
|
|
103779
|
+
/**
|
|
103780
|
+
* Gets how the nodes are organized within this tree view.
|
|
103781
|
+
*
|
|
103782
|
+
* @type {String}
|
|
103783
|
+
*/
|
|
103784
|
+
get hierarchy() {
|
|
103785
|
+
return this._hierarchy;
|
|
103503
103786
|
}
|
|
103504
103787
|
|
|
103505
|
-
|
|
103506
|
-
|
|
103507
|
-
|
|
103508
|
-
|
|
103509
|
-
|
|
103510
|
-
|
|
103511
|
-
|
|
103512
|
-
|
|
103513
|
-
|
|
103514
|
-
|
|
103515
|
-
|
|
103516
|
-
|
|
103788
|
+
/**
|
|
103789
|
+
* Adds a model to this tree view.
|
|
103790
|
+
*
|
|
103791
|
+
* The model will be automatically removed when destroyed.
|
|
103792
|
+
*
|
|
103793
|
+
* To automatically add each model as it's created, instead of manually calling this method each time,
|
|
103794
|
+
* provide a ````autoAddModels: true```` to the TreeViewPlugin constructor.
|
|
103795
|
+
*
|
|
103796
|
+
* @param {String} modelId ID of a model {@link Entity} in {@link Scene#models}.
|
|
103797
|
+
* @param {Object} [options] Options for model in the tree view.
|
|
103798
|
+
* @param {String} [options.rootName] Optional display name for the root node. Ordinary, for "containment"
|
|
103799
|
+
* and "storeys" hierarchy types, the tree would derive the root node name from the model's "IfcProject" element
|
|
103800
|
+
* name. This option allows to override that name when it is not suitable as a display name.
|
|
103801
|
+
*/
|
|
103802
|
+
addModel(modelId, options = {}) {
|
|
103803
|
+
if (!this._containerElement) {
|
|
103804
|
+
return;
|
|
103805
|
+
}
|
|
103806
|
+
const model = this.viewer.scene.models[modelId];
|
|
103807
|
+
if (!model) {
|
|
103808
|
+
throw "Model not found: " + modelId;
|
|
103809
|
+
}
|
|
103810
|
+
const metaModel = this.viewer.metaScene.metaModels[modelId];
|
|
103811
|
+
if (!metaModel) {
|
|
103812
|
+
this.error("MetaModel not found: " + modelId);
|
|
103813
|
+
return;
|
|
103814
|
+
}
|
|
103815
|
+
if (this._metaModels[modelId]) {
|
|
103816
|
+
this.warn("Model already added: " + modelId);
|
|
103817
|
+
return;
|
|
103818
|
+
}
|
|
103819
|
+
this._metaModels[modelId] = metaModel;
|
|
103820
|
+
model.on("destroyed", () => {
|
|
103821
|
+
this.removeModel(model.id);
|
|
103822
|
+
});
|
|
103823
|
+
this._createNodes();
|
|
103824
|
+
}
|
|
103825
|
+
|
|
103826
|
+
/**
|
|
103827
|
+
* Removes a model from this tree view.
|
|
103828
|
+
*
|
|
103829
|
+
* Does nothing if model not currently in tree view.
|
|
103830
|
+
*
|
|
103831
|
+
* @param {String} modelId ID of a model {@link Entity} in {@link Scene#models}.
|
|
103832
|
+
*/
|
|
103833
|
+
removeModel(modelId) {
|
|
103834
|
+
if (!this._containerElement) {
|
|
103835
|
+
return;
|
|
103836
|
+
}
|
|
103837
|
+
const metaModel = this._metaModels[modelId];
|
|
103838
|
+
if (!metaModel) {
|
|
103839
|
+
return;
|
|
103840
|
+
}
|
|
103841
|
+
delete this._metaModels[modelId];
|
|
103842
|
+
this._createNodes();
|
|
103843
|
+
}
|
|
103844
|
+
|
|
103845
|
+
/**
|
|
103846
|
+
* Highlights the tree view node that represents the given object {@link Entity}.
|
|
103847
|
+
*
|
|
103848
|
+
* This causes the tree view to collapse, then expand to reveal the node, then highlight the node.
|
|
103849
|
+
*
|
|
103850
|
+
* If a node is previously highlighted, de-highlights that node and collapses the tree first.
|
|
103851
|
+
*
|
|
103852
|
+
* Note that if the TreeViewPlugin was configured with ````pruneEmptyNodes: true```` (default configuration), then the
|
|
103853
|
+
* node won't exist in the tree if it has no Entitys in the {@link Scene}. in that case, nothing will happen.
|
|
103854
|
+
*
|
|
103855
|
+
* Within the DOM, the node is represented by an ````<li>```` element. This method will add a ````.highlighted-node```` class to
|
|
103856
|
+
* the element to make it appear highlighted, removing that class when de-highlighting it again. See the CSS rules
|
|
103857
|
+
* in the TreeViewPlugin examples for an example of that class.
|
|
103858
|
+
*
|
|
103859
|
+
* @param {String} objectId ID of the {@link Entity}.
|
|
103860
|
+
*/
|
|
103861
|
+
showNode(objectId) {
|
|
103862
|
+
if (this._showListItemElementId) {
|
|
103863
|
+
this.unShowNode();
|
|
103864
|
+
}
|
|
103865
|
+
const node = this._objectNodes[objectId];
|
|
103866
|
+
if (!node) {
|
|
103867
|
+
return; // Node may not exist for the given object if (this._pruneEmptyNodes == true)
|
|
103868
|
+
}
|
|
103869
|
+
const nodeId = node.nodeId;
|
|
103870
|
+
const switchElementId = "switch-" + nodeId;
|
|
103871
|
+
const switchElement = document.getElementById(switchElementId);
|
|
103872
|
+
if (switchElement) {
|
|
103873
|
+
this._expandSwitchElement(switchElement);
|
|
103874
|
+
switchElement.scrollIntoView();
|
|
103875
|
+
return;
|
|
103876
|
+
}
|
|
103877
|
+
const path = [];
|
|
103878
|
+
path.unshift(node);
|
|
103879
|
+
let parent = node.parent;
|
|
103880
|
+
while (parent) {
|
|
103881
|
+
path.unshift(parent);
|
|
103882
|
+
parent = parent.parent;
|
|
103883
|
+
}
|
|
103884
|
+
for (let i = 0, len = path.length; i < len; i++) {
|
|
103885
|
+
const node = path[i];
|
|
103886
|
+
const nodeId = node.nodeId;
|
|
103887
|
+
const switchElementId = "switch-" + nodeId;
|
|
103888
|
+
const switchElement = document.getElementById(switchElementId);
|
|
103889
|
+
if (switchElement) {
|
|
103890
|
+
this._expandSwitchElement(switchElement);
|
|
103891
|
+
}
|
|
103892
|
+
}
|
|
103893
|
+
const listItemElementId = 'node-' + nodeId;
|
|
103894
|
+
const listItemElement = document.getElementById(listItemElementId);
|
|
103895
|
+
listItemElement.scrollIntoView({block: "center"});
|
|
103896
|
+
listItemElement.classList.add("highlighted-node");
|
|
103897
|
+
this._showListItemElementId = listItemElementId;
|
|
103898
|
+
}
|
|
103899
|
+
|
|
103900
|
+
/**
|
|
103901
|
+
* De-highlights the node previously shown with {@link TreeViewPlugin#showNode}.
|
|
103902
|
+
*
|
|
103903
|
+
* Does nothing if no node is currently shown.
|
|
103904
|
+
*
|
|
103905
|
+
* If the node is currently scrolled into view, keeps the node in view.
|
|
103906
|
+
*/
|
|
103907
|
+
unShowNode() {
|
|
103908
|
+
if (!this._showListItemElementId) {
|
|
103909
|
+
return;
|
|
103910
|
+
}
|
|
103911
|
+
const listItemElement = document.getElementById(this._showListItemElementId);
|
|
103912
|
+
if (!listItemElement) {
|
|
103913
|
+
this._showListItemElementId = null;
|
|
103914
|
+
return;
|
|
103915
|
+
}
|
|
103916
|
+
listItemElement.classList.remove("highlighted-node");
|
|
103917
|
+
this._showListItemElementId = null;
|
|
103918
|
+
}
|
|
103919
|
+
|
|
103920
|
+
/**
|
|
103921
|
+
* Expands the tree to the given depth.
|
|
103922
|
+
*
|
|
103923
|
+
* Collapses the tree first.
|
|
103924
|
+
*
|
|
103925
|
+
* @param {Number} depth Depth to expand to.
|
|
103926
|
+
*/
|
|
103927
|
+
expandToDepth(depth) {
|
|
103928
|
+
this.collapse();
|
|
103929
|
+
const expand = (node, countDepth) => {
|
|
103930
|
+
if (countDepth === depth) {
|
|
103931
|
+
return;
|
|
103932
|
+
}
|
|
103933
|
+
const nodeId = node.nodeId;
|
|
103934
|
+
const switchElementId = "switch-" + nodeId;
|
|
103935
|
+
const switchElement = document.getElementById(switchElementId);
|
|
103936
|
+
if (switchElement) {
|
|
103937
|
+
this._expandSwitchElement(switchElement);
|
|
103938
|
+
const childNodes = node.children;
|
|
103939
|
+
for (var i = 0, len = childNodes.length; i < len; i++) {
|
|
103940
|
+
const childNode = childNodes[i];
|
|
103941
|
+
expand(childNode, countDepth + 1);
|
|
103942
|
+
}
|
|
103943
|
+
}
|
|
103944
|
+
};
|
|
103945
|
+
for (let i = 0, len = this._rootNodes.length; i < len; i++) {
|
|
103946
|
+
const rootNode = this._rootNodes[i];
|
|
103947
|
+
expand(rootNode, 0);
|
|
103948
|
+
}
|
|
103949
|
+
}
|
|
103950
|
+
|
|
103951
|
+
/**
|
|
103952
|
+
* Closes all the nodes in the tree.
|
|
103953
|
+
*/
|
|
103954
|
+
collapse() {
|
|
103955
|
+
for (let i = 0, len = this._rootNodes.length; i < len; i++) {
|
|
103956
|
+
const rootNode = this._rootNodes[i];
|
|
103957
|
+
const objectId = rootNode.objectId;
|
|
103958
|
+
this._collapseNode(objectId);
|
|
103959
|
+
}
|
|
103960
|
+
}
|
|
103961
|
+
|
|
103962
|
+
/**
|
|
103963
|
+
* Iterates over a subtree of the tree view's {@link TreeViewNode}s, calling the given callback for each
|
|
103964
|
+
* node in depth-first pre-order.
|
|
103965
|
+
*
|
|
103966
|
+
* @param {TreeViewNode} node Root of the subtree.
|
|
103967
|
+
* @param {Function} callback Callback called at each {@link TreeViewNode}, with the TreeViewNode given as the argument.
|
|
103968
|
+
*/
|
|
103969
|
+
withNodeTree(node, callback) {
|
|
103970
|
+
callback(node);
|
|
103971
|
+
const children = node.children;
|
|
103972
|
+
if (!children) {
|
|
103973
|
+
return;
|
|
103974
|
+
}
|
|
103975
|
+
for (let i = 0, len = children.length; i < len; i++) {
|
|
103976
|
+
this.withNodeTree(children[i], callback);
|
|
103977
|
+
}
|
|
103978
|
+
}
|
|
103979
|
+
|
|
103980
|
+
/**
|
|
103981
|
+
* Destroys this TreeViewPlugin.
|
|
103982
|
+
*/
|
|
103983
|
+
destroy() {
|
|
103984
|
+
if (!this._containerElement) {
|
|
103985
|
+
return;
|
|
103986
|
+
}
|
|
103987
|
+
this._metaModels = {};
|
|
103988
|
+
if (this._rootElement && !this._destroyed) {
|
|
103989
|
+
this._rootElement.parentNode.removeChild(this._rootElement);
|
|
103990
|
+
this._viewer.scene.off(this._onObjectVisibility);
|
|
103991
|
+
this._destroyed = true;
|
|
103992
|
+
}
|
|
103993
|
+
super.destroy();
|
|
103994
|
+
}
|
|
103995
|
+
|
|
103996
|
+
_createNodes() {
|
|
103997
|
+
if (this._rootElement) {
|
|
103998
|
+
this._rootElement.parentNode.removeChild(this._rootElement);
|
|
103999
|
+
this._rootElement = null;
|
|
104000
|
+
}
|
|
104001
|
+
|
|
104002
|
+
this._rootNodes = [];
|
|
104003
|
+
this._objectNodes = {};
|
|
104004
|
+
this._validate();
|
|
104005
|
+
if (this.valid || (this._hierarchy !== "storeys")) {
|
|
104006
|
+
this._createEnabledNodes();
|
|
104007
|
+
} else {
|
|
104008
|
+
this._createDisabledNodes();
|
|
104009
|
+
}
|
|
104010
|
+
}
|
|
104011
|
+
|
|
104012
|
+
_validate() {
|
|
104013
|
+
this.errors = [];
|
|
104014
|
+
switch (this._hierarchy) {
|
|
104015
|
+
case "storeys":
|
|
104016
|
+
this.valid = this._validateMetaModelForStoreysHierarchy();
|
|
104017
|
+
break;
|
|
104018
|
+
case "types":
|
|
104019
|
+
this.valid = (this._rootNodes.length > 0);
|
|
104020
|
+
break;
|
|
104021
|
+
case "containment":
|
|
104022
|
+
default:
|
|
104023
|
+
this.valid = (this._rootNodes.length > 0);
|
|
103517
104024
|
break;
|
|
103518
104025
|
}
|
|
103519
104026
|
return this.valid;
|
|
103520
104027
|
}
|
|
103521
104028
|
|
|
104029
|
+
_validateMetaModelForStoreysHierarchy(level = 0, ctx, buildingNode) {
|
|
104030
|
+
// ctx = ctx || {
|
|
104031
|
+
// foundIFCBuildingStoreys: false
|
|
104032
|
+
// };
|
|
104033
|
+
// const metaObjectType = metaObject.type;
|
|
104034
|
+
// const children = metaObject.children;
|
|
104035
|
+
// if (metaObjectType === "IfcBuilding") {
|
|
104036
|
+
// buildingNode = true;
|
|
104037
|
+
// } else if (metaObjectType === "IfcBuildingStorey") {
|
|
104038
|
+
// if (!buildingNode) {
|
|
104039
|
+
// errors.push("Can't build storeys hierarchy: IfcBuildingStorey found without parent IfcBuilding");
|
|
104040
|
+
// return false;
|
|
104041
|
+
// }
|
|
104042
|
+
// ctx.foundIFCBuildingStoreys = true;
|
|
104043
|
+
// }
|
|
104044
|
+
// if (children) {
|
|
104045
|
+
// for (let i = 0, len = children.length; i < len; i++) {
|
|
104046
|
+
// const childMetaObject = children[i];
|
|
104047
|
+
// if (!this._validateMetaModelForStoreysHierarchy(childMetaObject, errors, level + 1, ctx, buildingNode)) {
|
|
104048
|
+
// return false;
|
|
104049
|
+
// }
|
|
104050
|
+
// }
|
|
104051
|
+
// }
|
|
104052
|
+
// if (level === 0) {
|
|
104053
|
+
// if (!ctx.foundIFCBuildingStoreys) {
|
|
104054
|
+
// // errors.push("Can't build storeys hierarchy: no IfcBuildingStoreys found");
|
|
104055
|
+
// }
|
|
104056
|
+
// }
|
|
104057
|
+
return true;
|
|
104058
|
+
}
|
|
104059
|
+
|
|
103522
104060
|
_createEnabledNodes() {
|
|
103523
104061
|
if (this._pruneEmptyNodes) {
|
|
103524
104062
|
this._findEmptyNodes();
|
|
@@ -103527,7 +104065,7 @@ class ModelTreeView {
|
|
|
103527
104065
|
case "storeys":
|
|
103528
104066
|
this._createStoreysNodes();
|
|
103529
104067
|
if (this._rootNodes.length === 0) {
|
|
103530
|
-
this.
|
|
104068
|
+
this.error("Failed to build storeys hierarchy");
|
|
103531
104069
|
}
|
|
103532
104070
|
break;
|
|
103533
104071
|
case "types":
|
|
@@ -103547,32 +104085,42 @@ class ModelTreeView {
|
|
|
103547
104085
|
|
|
103548
104086
|
_createDisabledNodes() {
|
|
103549
104087
|
|
|
103550
|
-
const metaObject = this._rootMetaObject;
|
|
103551
|
-
const metaObjectType = metaObject.type;
|
|
103552
|
-
const metaObjectName = metaObject.name;
|
|
103553
|
-
|
|
103554
|
-
const rootName = ((metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType);
|
|
103555
|
-
|
|
103556
104088
|
const ul = document.createElement('ul');
|
|
103557
|
-
const li = document.createElement('li');
|
|
103558
|
-
ul.appendChild(li);
|
|
103559
|
-
this._containerElement.appendChild(ul);
|
|
103560
104089
|
this._rootElement = ul;
|
|
104090
|
+
this._containerElement.appendChild(ul);
|
|
103561
104091
|
|
|
103562
|
-
const
|
|
103563
|
-
|
|
103564
|
-
|
|
103565
|
-
|
|
103566
|
-
|
|
103567
|
-
|
|
104092
|
+
const rootMetaObjects = this._viewer.metaScene.rootMetaObjects;
|
|
104093
|
+
for (let i = 0, len = rootMetaObjects.length; i < len; i++) {
|
|
104094
|
+
const rootMetaObject = rootMetaObjects[i];
|
|
104095
|
+
const metaObjectType = rootMetaObject.type;
|
|
104096
|
+
const metaObjectName = rootMetaObject.name;
|
|
104097
|
+
const rootName = ((metaObjectName && metaObjectName !== ""
|
|
104098
|
+
&& metaObjectName !== "Undefined"
|
|
104099
|
+
&& metaObjectName !== "Default") ? metaObjectName : metaObjectType);
|
|
104100
|
+
const li = document.createElement('li');
|
|
104101
|
+
ul.appendChild(li);
|
|
104102
|
+
const switchElement = document.createElement('a');
|
|
104103
|
+
switchElement.href = '#';
|
|
104104
|
+
switchElement.textContent = '!';
|
|
104105
|
+
switchElement.classList.add('warn');
|
|
104106
|
+
switchElement.classList.add('warning');
|
|
104107
|
+
li.appendChild(switchElement);
|
|
104108
|
+
const span = document.createElement('span');
|
|
104109
|
+
span.textContent = rootName;
|
|
104110
|
+
li.appendChild(span);
|
|
104111
|
+
}
|
|
104112
|
+
}
|
|
103568
104113
|
|
|
103569
|
-
|
|
103570
|
-
|
|
103571
|
-
|
|
104114
|
+
|
|
104115
|
+
_findEmptyNodes() {
|
|
104116
|
+
const rootMetaObjects = this._viewer.metaScene.rootMetaObjects;
|
|
104117
|
+
for (let i = 0, len = rootMetaObjects.length; i < len; i++) {
|
|
104118
|
+
this._findEmptyNodes2(rootMetaObjects[i]);
|
|
104119
|
+
}
|
|
103572
104120
|
}
|
|
103573
104121
|
|
|
103574
|
-
|
|
103575
|
-
const viewer = this.
|
|
104122
|
+
_findEmptyNodes2(metaObject, countEntities = 0) {
|
|
104123
|
+
const viewer = this.viewer;
|
|
103576
104124
|
const scene = viewer.scene;
|
|
103577
104125
|
const children = metaObject.children;
|
|
103578
104126
|
const objectId = metaObject.id;
|
|
@@ -103584,18 +104132,21 @@ class ModelTreeView {
|
|
|
103584
104132
|
if (children) {
|
|
103585
104133
|
for (let i = 0, len = children.length; i < len; i++) {
|
|
103586
104134
|
const childMetaObject = children[i];
|
|
103587
|
-
childMetaObject._countEntities = this.
|
|
104135
|
+
childMetaObject._countEntities = this._findEmptyNodes2(childMetaObject);
|
|
103588
104136
|
metaObject._countEntities += childMetaObject._countEntities;
|
|
103589
104137
|
}
|
|
103590
104138
|
}
|
|
103591
104139
|
return metaObject._countEntities;
|
|
103592
104140
|
}
|
|
103593
104141
|
|
|
103594
|
-
_createStoreysNodes(
|
|
103595
|
-
|
|
103596
|
-
|
|
103597
|
-
|
|
103598
|
-
|
|
104142
|
+
_createStoreysNodes() {
|
|
104143
|
+
const rootMetaObjects = this._viewer.metaScene.rootMetaObjects;
|
|
104144
|
+
for (let id in rootMetaObjects) {
|
|
104145
|
+
this._createStoreysNodes2(rootMetaObjects[id], null, null, null);
|
|
104146
|
+
}
|
|
104147
|
+
}
|
|
104148
|
+
|
|
104149
|
+
_createStoreysNodes2(metaObject, buildingNode, storeyNode, typeNodes) {
|
|
103599
104150
|
if (this._pruneEmptyNodes && (metaObject._countEntities === 0)) {
|
|
103600
104151
|
return;
|
|
103601
104152
|
}
|
|
@@ -103605,7 +104156,7 @@ class ModelTreeView {
|
|
|
103605
104156
|
const objectId = metaObject.id;
|
|
103606
104157
|
if (metaObjectType === "IfcBuilding") {
|
|
103607
104158
|
buildingNode = {
|
|
103608
|
-
nodeId:
|
|
104159
|
+
nodeId: objectId,
|
|
103609
104160
|
objectId: objectId,
|
|
103610
104161
|
title: this._rootName || ((metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType),
|
|
103611
104162
|
type: metaObjectType,
|
|
@@ -103620,11 +104171,11 @@ class ModelTreeView {
|
|
|
103620
104171
|
this._objectNodes[buildingNode.objectId] = buildingNode;
|
|
103621
104172
|
} else if (metaObjectType === "IfcBuildingStorey") {
|
|
103622
104173
|
if (!buildingNode) {
|
|
103623
|
-
this.
|
|
104174
|
+
this.error("Failed to build storeys hierarchy for model '" + this.metaModel.id + "' - model does not have an IfcBuilding object, or is not an IFC model");
|
|
103624
104175
|
return;
|
|
103625
104176
|
}
|
|
103626
104177
|
storeyNode = {
|
|
103627
|
-
nodeId:
|
|
104178
|
+
nodeId: objectId,
|
|
103628
104179
|
objectId: objectId,
|
|
103629
104180
|
title: (metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType,
|
|
103630
104181
|
type: metaObjectType,
|
|
@@ -103647,7 +104198,7 @@ class ModelTreeView {
|
|
|
103647
104198
|
let typeNode = typeNodes[metaObjectType];
|
|
103648
104199
|
if (!typeNode) {
|
|
103649
104200
|
const typeNodeObjectId = storeyNode.objectId + "." + metaObjectType;
|
|
103650
|
-
const typeNodeNodeId =
|
|
104201
|
+
const typeNodeNodeId = typeNodeObjectId;
|
|
103651
104202
|
typeNode = {
|
|
103652
104203
|
nodeId: typeNodeNodeId,
|
|
103653
104204
|
objectId: typeNodeObjectId,
|
|
@@ -103665,7 +104216,7 @@ class ModelTreeView {
|
|
|
103665
104216
|
typeNodes[metaObjectType] = typeNode;
|
|
103666
104217
|
}
|
|
103667
104218
|
const node = {
|
|
103668
|
-
nodeId:
|
|
104219
|
+
nodeId: objectId,
|
|
103669
104220
|
objectId: objectId,
|
|
103670
104221
|
title: (metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType,
|
|
103671
104222
|
type: metaObjectType,
|
|
@@ -103684,12 +104235,19 @@ class ModelTreeView {
|
|
|
103684
104235
|
if (children) {
|
|
103685
104236
|
for (let i = 0, len = children.length; i < len; i++) {
|
|
103686
104237
|
const childMetaObject = children[i];
|
|
103687
|
-
this.
|
|
104238
|
+
this._createStoreysNodes2(childMetaObject, buildingNode, storeyNode, typeNodes);
|
|
103688
104239
|
}
|
|
103689
104240
|
}
|
|
103690
104241
|
}
|
|
103691
104242
|
|
|
103692
|
-
_createTypesNodes(
|
|
104243
|
+
_createTypesNodes() {
|
|
104244
|
+
const rootMetaObjects = this._viewer.metaScene.rootMetaObjects;
|
|
104245
|
+
for (let id in rootMetaObjects) {
|
|
104246
|
+
this._createTypesNodes2(rootMetaObjects[id], null, null);
|
|
104247
|
+
}
|
|
104248
|
+
}
|
|
104249
|
+
|
|
104250
|
+
_createTypesNodes2(metaObject, rootNode, typeNodes) {
|
|
103693
104251
|
if (this._pruneEmptyNodes && (metaObject._countEntities === 0)) {
|
|
103694
104252
|
return;
|
|
103695
104253
|
}
|
|
@@ -103697,9 +104255,9 @@ class ModelTreeView {
|
|
|
103697
104255
|
const metaObjectName = metaObject.name;
|
|
103698
104256
|
const children = metaObject.children;
|
|
103699
104257
|
const objectId = metaObject.id;
|
|
103700
|
-
if (metaObject.
|
|
104258
|
+
if (!metaObject.parent) {
|
|
103701
104259
|
rootNode = {
|
|
103702
|
-
nodeId:
|
|
104260
|
+
nodeId: objectId,
|
|
103703
104261
|
objectId: objectId,
|
|
103704
104262
|
title: this._rootName || ((metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType),
|
|
103705
104263
|
type: metaObjectType,
|
|
@@ -103721,7 +104279,7 @@ class ModelTreeView {
|
|
|
103721
104279
|
let typeNode = typeNodes[metaObjectType];
|
|
103722
104280
|
if (!typeNode) {
|
|
103723
104281
|
typeNode = {
|
|
103724
|
-
nodeId:
|
|
104282
|
+
nodeId: rootNode.objectId + "." + metaObjectType,
|
|
103725
104283
|
objectId: rootNode.objectId + "." + metaObjectType,
|
|
103726
104284
|
title: metaObjectType,
|
|
103727
104285
|
type: metaObjectType,
|
|
@@ -103737,7 +104295,7 @@ class ModelTreeView {
|
|
|
103737
104295
|
typeNodes[metaObjectType] = typeNode;
|
|
103738
104296
|
}
|
|
103739
104297
|
const node = {
|
|
103740
|
-
nodeId:
|
|
104298
|
+
nodeId: objectId,
|
|
103741
104299
|
objectId: objectId,
|
|
103742
104300
|
title: (metaObjectName && metaObjectName !== "" && metaObjectName !== "Default") ? metaObjectName : metaObjectType,
|
|
103743
104301
|
type: metaObjectType,
|
|
@@ -103756,12 +104314,19 @@ class ModelTreeView {
|
|
|
103756
104314
|
if (children) {
|
|
103757
104315
|
for (let i = 0, len = children.length; i < len; i++) {
|
|
103758
104316
|
const childMetaObject = children[i];
|
|
103759
|
-
this.
|
|
104317
|
+
this._createTypesNodes2(childMetaObject, rootNode, typeNodes);
|
|
103760
104318
|
}
|
|
103761
104319
|
}
|
|
103762
104320
|
}
|
|
103763
104321
|
|
|
103764
|
-
_createContainmentNodes(
|
|
104322
|
+
_createContainmentNodes() {
|
|
104323
|
+
const rootMetaObjects = this._viewer.metaScene.rootMetaObjects;
|
|
104324
|
+
for (let id in rootMetaObjects) {
|
|
104325
|
+
this._createContainmentNodes2(rootMetaObjects[id], null);
|
|
104326
|
+
}
|
|
104327
|
+
}
|
|
104328
|
+
|
|
104329
|
+
_createContainmentNodes2(metaObject, parent) {
|
|
103765
104330
|
if (this._pruneEmptyNodes && (metaObject._countEntities === 0)) {
|
|
103766
104331
|
return;
|
|
103767
104332
|
}
|
|
@@ -103770,7 +104335,7 @@ class ModelTreeView {
|
|
|
103770
104335
|
const children = metaObject.children;
|
|
103771
104336
|
const objectId = metaObject.id;
|
|
103772
104337
|
const node = {
|
|
103773
|
-
nodeId:
|
|
104338
|
+
nodeId: objectId,
|
|
103774
104339
|
objectId: objectId,
|
|
103775
104340
|
title: (!parent) ? (this._rootName || metaObjectName) : (metaObjectName && metaObjectName !== "" && metaObjectName !== "Undefined" && metaObjectName !== "Default") ? metaObjectName : metaObjectType,
|
|
103776
104341
|
type: metaObjectType,
|
|
@@ -103791,7 +104356,7 @@ class ModelTreeView {
|
|
|
103791
104356
|
if (children) {
|
|
103792
104357
|
for (let i = 0, len = children.length; i < len; i++) {
|
|
103793
104358
|
const childMetaObject = children[i];
|
|
103794
|
-
this.
|
|
104359
|
+
this._createContainmentNodes2(childMetaObject, node);
|
|
103795
104360
|
}
|
|
103796
104361
|
}
|
|
103797
104362
|
}
|
|
@@ -103821,7 +104386,7 @@ class ModelTreeView {
|
|
|
103821
104386
|
}
|
|
103822
104387
|
|
|
103823
104388
|
_getSpatialSortFunc() { // Creates cached sort func with Viewer in scope
|
|
103824
|
-
const viewer = this.
|
|
104389
|
+
const viewer = this.viewer;
|
|
103825
104390
|
const scene = viewer.scene;
|
|
103826
104391
|
const camera = scene.camera;
|
|
103827
104392
|
const metaScene = viewer.metaScene;
|
|
@@ -103866,8 +104431,7 @@ class ModelTreeView {
|
|
|
103866
104431
|
}
|
|
103867
104432
|
|
|
103868
104433
|
_synchNodesToEntities() {
|
|
103869
|
-
const
|
|
103870
|
-
const objectIds = rootMetaObject.getObjectIDsInSubtree();
|
|
104434
|
+
const objectIds = Object.keys(this.viewer.metaScene.metaObjects);
|
|
103871
104435
|
const metaObjects = this._viewer.metaScene.metaObjects;
|
|
103872
104436
|
const objects = this._viewer.scene.objects;
|
|
103873
104437
|
for (let i = 0, len = objectIds.length; i < len; i++) {
|
|
@@ -103934,7 +104498,7 @@ class ModelTreeView {
|
|
|
103934
104498
|
//const nodeId = this._objectToNodeID(node.objectId);
|
|
103935
104499
|
const nodeId = node.nodeId;
|
|
103936
104500
|
if (node.xrayed) {
|
|
103937
|
-
|
|
104501
|
+
nodeElement.classList.add('xrayed-node');
|
|
103938
104502
|
}
|
|
103939
104503
|
nodeElement.id = 'node-' + nodeId;
|
|
103940
104504
|
if (node.children.length > 0) {
|
|
@@ -103944,7 +104508,7 @@ class ModelTreeView {
|
|
|
103944
104508
|
switchElement.id = switchElementId;
|
|
103945
104509
|
switchElement.textContent = '+';
|
|
103946
104510
|
switchElement.classList.add('plus');
|
|
103947
|
-
switchElement.addEventListener('click', this.
|
|
104511
|
+
switchElement.addEventListener('click', this._switchExpandHandler);
|
|
103948
104512
|
nodeElement.appendChild(switchElement);
|
|
103949
104513
|
}
|
|
103950
104514
|
const checkbox = document.createElement('input');
|
|
@@ -103958,19 +104522,19 @@ class ModelTreeView {
|
|
|
103958
104522
|
span.textContent = node.title;
|
|
103959
104523
|
nodeElement.appendChild(span);
|
|
103960
104524
|
span.oncontextmenu = (e) => {
|
|
103961
|
-
this.
|
|
104525
|
+
this.fire("contextmenu", {
|
|
103962
104526
|
event: e,
|
|
103963
104527
|
viewer: this._viewer,
|
|
103964
|
-
treeViewPlugin: this
|
|
104528
|
+
treeViewPlugin: this,
|
|
103965
104529
|
treeViewNode: node
|
|
103966
104530
|
});
|
|
103967
104531
|
e.preventDefault();
|
|
103968
104532
|
};
|
|
103969
104533
|
span.onclick = (e) => {
|
|
103970
|
-
this.
|
|
104534
|
+
this.fire("nodeTitleClicked", {
|
|
103971
104535
|
event: e,
|
|
103972
104536
|
viewer: this._viewer,
|
|
103973
|
-
treeViewPlugin: this
|
|
104537
|
+
treeViewPlugin: this,
|
|
103974
104538
|
treeViewNode: node
|
|
103975
104539
|
});
|
|
103976
104540
|
e.preventDefault();
|
|
@@ -103978,103 +104542,6 @@ class ModelTreeView {
|
|
|
103978
104542
|
return nodeElement;
|
|
103979
104543
|
}
|
|
103980
104544
|
|
|
103981
|
-
/**
|
|
103982
|
-
* @private
|
|
103983
|
-
* @param depth
|
|
103984
|
-
*/
|
|
103985
|
-
expandToDepth(depth) {
|
|
103986
|
-
const expand = (node, countDepth) => {
|
|
103987
|
-
if (countDepth === depth) {
|
|
103988
|
-
return;
|
|
103989
|
-
}
|
|
103990
|
-
const nodeId = node.nodeId;
|
|
103991
|
-
const switchElementId = "switch-" + nodeId;
|
|
103992
|
-
const switchElement = document.getElementById(switchElementId);
|
|
103993
|
-
if (switchElement) {
|
|
103994
|
-
this._expandSwitchElement(switchElement);
|
|
103995
|
-
const childNodes = node.children;
|
|
103996
|
-
for (var i = 0, len = childNodes.length; i < len; i++) {
|
|
103997
|
-
const childNode = childNodes[i];
|
|
103998
|
-
expand(childNode, countDepth + 1);
|
|
103999
|
-
}
|
|
104000
|
-
}
|
|
104001
|
-
};
|
|
104002
|
-
for (let i = 0, len = this._rootNodes.length; i < len; i++) {
|
|
104003
|
-
const rootNode = this._rootNodes[i];
|
|
104004
|
-
expand(rootNode, 0);
|
|
104005
|
-
}
|
|
104006
|
-
}
|
|
104007
|
-
|
|
104008
|
-
/**
|
|
104009
|
-
* @private
|
|
104010
|
-
*/
|
|
104011
|
-
collapse() {
|
|
104012
|
-
for (let i = 0, len = this._rootNodes.length; i < len; i++) {
|
|
104013
|
-
const rootNode = this._rootNodes[i];
|
|
104014
|
-
const objectId = rootNode.objectId;
|
|
104015
|
-
this._collapseNode(objectId);
|
|
104016
|
-
}
|
|
104017
|
-
}
|
|
104018
|
-
|
|
104019
|
-
/**
|
|
104020
|
-
* @private
|
|
104021
|
-
* @param objectId
|
|
104022
|
-
*/
|
|
104023
|
-
showNode(objectId) {
|
|
104024
|
-
if (this._showListItemElementId) {
|
|
104025
|
-
this.unShowNode();
|
|
104026
|
-
}
|
|
104027
|
-
const node = this._objectNodes[objectId];
|
|
104028
|
-
if (!node) {
|
|
104029
|
-
return; // Node may not exist for the given object if (this._pruneEmptyNodes == true)
|
|
104030
|
-
}
|
|
104031
|
-
const nodeId = node.nodeId;
|
|
104032
|
-
const switchElementId = "switch-" + nodeId;
|
|
104033
|
-
const switchElement = document.getElementById(switchElementId);
|
|
104034
|
-
if (switchElement) {
|
|
104035
|
-
this._expandSwitchElement(switchElement);
|
|
104036
|
-
switchElement.scrollIntoView();
|
|
104037
|
-
return;
|
|
104038
|
-
}
|
|
104039
|
-
const path = [];
|
|
104040
|
-
path.unshift(node);
|
|
104041
|
-
let parent = node.parent;
|
|
104042
|
-
while (parent) {
|
|
104043
|
-
path.unshift(parent);
|
|
104044
|
-
parent = parent.parent;
|
|
104045
|
-
}
|
|
104046
|
-
for (let i = 0, len = path.length; i < len; i++) {
|
|
104047
|
-
const node = path[i];
|
|
104048
|
-
const nodeId = node.nodeId;
|
|
104049
|
-
const switchElementId = "switch-" + nodeId;
|
|
104050
|
-
const switchElement = document.getElementById(switchElementId);
|
|
104051
|
-
if (switchElement) {
|
|
104052
|
-
this._expandSwitchElement(switchElement);
|
|
104053
|
-
}
|
|
104054
|
-
}
|
|
104055
|
-
const listItemElementId = 'node-' + nodeId;
|
|
104056
|
-
const listItemElement = document.getElementById(listItemElementId);
|
|
104057
|
-
listItemElement.scrollIntoView({block: "center"});
|
|
104058
|
-
listItemElement.classList.add("highlighted-node");
|
|
104059
|
-
this._showListItemElementId = listItemElementId;
|
|
104060
|
-
}
|
|
104061
|
-
|
|
104062
|
-
/**
|
|
104063
|
-
* @private
|
|
104064
|
-
*/
|
|
104065
|
-
unShowNode() {
|
|
104066
|
-
if (!this._showListItemElementId) {
|
|
104067
|
-
return;
|
|
104068
|
-
}
|
|
104069
|
-
const listItemElement = document.getElementById(this._showListItemElementId);
|
|
104070
|
-
if (!listItemElement) {
|
|
104071
|
-
this._showListItemElementId = null;
|
|
104072
|
-
return;
|
|
104073
|
-
}
|
|
104074
|
-
listItemElement.classList.remove("highlighted-node");
|
|
104075
|
-
this._showListItemElementId = null;
|
|
104076
|
-
}
|
|
104077
|
-
|
|
104078
104545
|
_expandSwitchElement(switchElement) {
|
|
104079
104546
|
const parentElement = switchElement.parentElement;
|
|
104080
104547
|
const expanded = parentElement.getElementsByTagName('li')[0];
|
|
@@ -104082,7 +104549,7 @@ class ModelTreeView {
|
|
|
104082
104549
|
return;
|
|
104083
104550
|
}
|
|
104084
104551
|
const nodeId = parentElement.id.replace('node-', '');
|
|
104085
|
-
const objectId =
|
|
104552
|
+
const objectId = nodeId;
|
|
104086
104553
|
const switchNode = this._objectNodes[objectId];
|
|
104087
104554
|
const childNodes = switchNode.children;
|
|
104088
104555
|
const nodeElements = childNodes.map((node) => {
|
|
@@ -104096,12 +104563,12 @@ class ModelTreeView {
|
|
|
104096
104563
|
switchElement.classList.remove('plus');
|
|
104097
104564
|
switchElement.classList.add('minus');
|
|
104098
104565
|
switchElement.textContent = '-';
|
|
104099
|
-
switchElement.removeEventListener('click', this.
|
|
104100
|
-
switchElement.addEventListener('click', this.
|
|
104566
|
+
switchElement.removeEventListener('click', this._switchExpandHandler);
|
|
104567
|
+
switchElement.addEventListener('click', this._switchCollapseHandler);
|
|
104101
104568
|
}
|
|
104102
104569
|
|
|
104103
104570
|
_collapseNode(objectId) {
|
|
104104
|
-
const nodeId =
|
|
104571
|
+
const nodeId = objectId;
|
|
104105
104572
|
const switchElementId = "switch-" + nodeId;
|
|
104106
104573
|
const switchElement = document.getElementById(switchElementId);
|
|
104107
104574
|
this._collapseSwitchElement(switchElement);
|
|
@@ -104123,641 +104590,8 @@ class ModelTreeView {
|
|
|
104123
104590
|
switchElement.classList.remove('minus');
|
|
104124
104591
|
switchElement.classList.add('plus');
|
|
104125
104592
|
switchElement.textContent = '+';
|
|
104126
|
-
switchElement.removeEventListener('click', this.
|
|
104127
|
-
switchElement.addEventListener('click', this.
|
|
104128
|
-
}
|
|
104129
|
-
|
|
104130
|
-
/**
|
|
104131
|
-
* Destroys this ModelTreeView.
|
|
104132
|
-
* @private
|
|
104133
|
-
*/
|
|
104134
|
-
destroy() {
|
|
104135
|
-
if (this._rootElement && !this._destroyed) {
|
|
104136
|
-
this._rootElement.parentNode.removeChild(this._rootElement);
|
|
104137
|
-
this._viewer.scene.off(this._onObjectVisibility);
|
|
104138
|
-
this._destroyed = true;
|
|
104139
|
-
idMap.removeItem(this._id);
|
|
104140
|
-
}
|
|
104141
|
-
}
|
|
104142
|
-
}
|
|
104143
|
-
|
|
104144
|
-
/**
|
|
104145
|
-
* @desc A {@link Viewer} plugin that provides an HTML tree view to navigate the IFC elements in models.
|
|
104146
|
-
* <br>
|
|
104147
|
-
*
|
|
104148
|
-
* <a href="https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_WestRiverSideHospital" style="border: 1px solid black;"><img src="http://xeokit.io/img/docs/TreeViewPlugin/TreeViewPlugin.png"></a>
|
|
104149
|
-
*
|
|
104150
|
-
* [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_WestRiverSideHospital)]
|
|
104151
|
-
*
|
|
104152
|
-
* ## Overview
|
|
104153
|
-
*
|
|
104154
|
-
* * A fast HTML tree view, with zero external dependencies, that works with huge numbers of objects.
|
|
104155
|
-
* * Each tree node has a checkbox to control the visibility of its object.
|
|
104156
|
-
* * Has three hierarchy modes: "containment", "types" and "storeys".
|
|
104157
|
-
* * Automatically contains all models (that have metadata) that are currently in the {@link Scene}.
|
|
104158
|
-
* * Sorts tree nodes by default - spatially, from top-to-bottom for ````IfcBuildingStorey```` nodes, and alphanumerically for other nodes.
|
|
104159
|
-
* * Allows custom CSS styling.
|
|
104160
|
-
* * Use {@link ContextMenu} to create a context menu for the tree nodes.
|
|
104161
|
-
*
|
|
104162
|
-
* ## Credits
|
|
104163
|
-
*
|
|
104164
|
-
* TreeViewPlugin is based on techniques described in [*Super Fast Tree View in JavaScript*](https://chrissmith.xyz/super-fast-tree-view-in-javascript/) by [Chris Smith](https://twitter.com/chris22smith).
|
|
104165
|
-
*
|
|
104166
|
-
* ## Usage
|
|
104167
|
-
*
|
|
104168
|
-
* In the example below, we'll add a TreeViewPlugin which, by default, will automatically show the structural
|
|
104169
|
-
* hierarchy of the IFC elements in each model we load.
|
|
104170
|
-
*
|
|
104171
|
-
* Then we'll use an {@link XKTLoaderPlugin} to load the Schependomlaan model from an
|
|
104172
|
-
* [.xkt file](https://github.com/xeokit/xeokit-sdk/tree/master/examples/models/xkt/schependomlaan).
|
|
104173
|
-
*
|
|
104174
|
-
* [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/#BIMOffline_XKT_Schependomlaan)]
|
|
104175
|
-
*
|
|
104176
|
-
* ````javascript
|
|
104177
|
-
* import {Viewer, XKTLoaderPlugin, TreeViewPlugin} from "xeokit-sdk.es.js";
|
|
104178
|
-
*
|
|
104179
|
-
* const viewer = new Viewer({
|
|
104180
|
-
* canvasId: "myCanvas",
|
|
104181
|
-
* transparent: true
|
|
104182
|
-
* });
|
|
104183
|
-
*
|
|
104184
|
-
* viewer.camera.eye = [-2.56, 8.38, 8.27];
|
|
104185
|
-
* viewer.camera.look = [13.44, 3.31, -14.83];
|
|
104186
|
-
* viewer.camera.up = [0.10, 0.98, -0.14];
|
|
104187
|
-
*
|
|
104188
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104189
|
-
* containerElement: document.getElementById("myTreeViewContainer")
|
|
104190
|
-
* });
|
|
104191
|
-
*
|
|
104192
|
-
* const xktLoader = new XKTLoaderPlugin(viewer);
|
|
104193
|
-
*
|
|
104194
|
-
* const model = xktLoader.load({
|
|
104195
|
-
* id: "myModel",
|
|
104196
|
-
* src: "./models/xkt/Schependomlaan.xkt",
|
|
104197
|
-
* edges: true
|
|
104198
|
-
* });
|
|
104199
|
-
* ````
|
|
104200
|
-
*
|
|
104201
|
-
* ## Manually Adding Models
|
|
104202
|
-
*
|
|
104203
|
-
* Instead of adding models automatically, we can control which models appear in our TreeViewPlugin by adding them manually.
|
|
104204
|
-
*
|
|
104205
|
-
* In the next example, we'll configure the TreeViewPlugin to not add models automatically. Then, once the model
|
|
104206
|
-
* has loaded, we'll add it manually using {@link TreeViewPlugin#addModel}.
|
|
104207
|
-
*
|
|
104208
|
-
* ````javascript
|
|
104209
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104210
|
-
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
104211
|
-
* autoAddModels: false // <<---------------- Don't auto-add models
|
|
104212
|
-
* });
|
|
104213
|
-
*
|
|
104214
|
-
* const xktLoader = new XKTLoaderPlugin(viewer);
|
|
104215
|
-
*
|
|
104216
|
-
* const model = xktLoader.load({
|
|
104217
|
-
* id: "myModel",
|
|
104218
|
-
* src: "./models/xkt/Schependomlaan.xkt",
|
|
104219
|
-
* edges: true
|
|
104220
|
-
* });
|
|
104221
|
-
*
|
|
104222
|
-
* model.on("loaded", () => {
|
|
104223
|
-
* treeView.addModel(model.id);
|
|
104224
|
-
* });
|
|
104225
|
-
* ````
|
|
104226
|
-
*
|
|
104227
|
-
* Adding models manually also allows us to set some options for the model. For example, the ````rootName```` option allows us to provide a custom name for
|
|
104228
|
-
* the root node, which is sometimes desirable when the model's "IfcProject" element's name is not suitable:
|
|
104229
|
-
*
|
|
104230
|
-
* ````javascript
|
|
104231
|
-
* model.on("loaded", () => {
|
|
104232
|
-
* treeView.addModel(model.id, {
|
|
104233
|
-
* rootName: "Schependomlaan Model"
|
|
104234
|
-
* });
|
|
104235
|
-
* });
|
|
104236
|
-
* ````
|
|
104237
|
-
*
|
|
104238
|
-
* ## Initially Expanding the Hierarchy
|
|
104239
|
-
*
|
|
104240
|
-
* We can also configure TreeViewPlugin to initially expand each model's nodes to a given depth.
|
|
104241
|
-
*
|
|
104242
|
-
* Let's automatically expand the first three nodes from the root, for every model added:
|
|
104243
|
-
*
|
|
104244
|
-
* ````javascript
|
|
104245
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104246
|
-
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
104247
|
-
* autoExpandDepth: 3
|
|
104248
|
-
* });
|
|
104249
|
-
* ````
|
|
104250
|
-
*
|
|
104251
|
-
* ## Showing a Node by ID
|
|
104252
|
-
*
|
|
104253
|
-
* We can show a given node using its ID. This causes the TreeViewPlugin to collapse, then expand and scroll the node into view, then highlight the node.
|
|
104254
|
-
*
|
|
104255
|
-
* See the documentation for the {@link TreeViewPlugin#showNode} method for more information, including how to define a custom highlighted appearance for the node using CSS.
|
|
104256
|
-
*
|
|
104257
|
-
* Let's make the TreeViewPlugin show the node corresponding to whatever object {@link Entity} that we pick:
|
|
104258
|
-
*
|
|
104259
|
-
* ````javascript
|
|
104260
|
-
* viewer.cameraControl.on("picked", function (e) {
|
|
104261
|
-
* var objectId = e.entity.id;
|
|
104262
|
-
* treeView.showNode(objectId);
|
|
104263
|
-
* });
|
|
104264
|
-
* ````
|
|
104265
|
-
*
|
|
104266
|
-
* This will de-highlight any node that was previously shown by this method.
|
|
104267
|
-
*
|
|
104268
|
-
* Note that this method only works if the picked {@link Entity} is an object that belongs to a model that's represented in the TreeViewPlugin.
|
|
104269
|
-
*
|
|
104270
|
-
* ## Customizing Appearance
|
|
104271
|
-
*
|
|
104272
|
-
* We can customize the appearance of our TreeViewPlugin by defining custom CSS for its HTML
|
|
104273
|
-
* elements. See our example's [source code](https://github.com/xeokit/xeokit-sdk/blob/master/examples/BIMOffline_XKT_Schependomlaan.html)
|
|
104274
|
-
* for an example of custom CSS rules.
|
|
104275
|
-
*
|
|
104276
|
-
* ## Model Hierarchies
|
|
104277
|
-
*
|
|
104278
|
-
* TreeViewPlugin has three hierarchies for organizing its nodes:
|
|
104279
|
-
*
|
|
104280
|
-
* * "containment" - organizes the tree nodes to indicate the containment hierarchy of the {@link MetaObject}s.
|
|
104281
|
-
* * "types" - groups nodes by their IFC types.
|
|
104282
|
-
* * "storeys" - groups nodes within their ````IfcBuildingStoreys````, and sub-groups them by their IFC types.
|
|
104283
|
-
*
|
|
104284
|
-
* <br>
|
|
104285
|
-
* The table below shows what the hierarchies look like:
|
|
104286
|
-
* <br>
|
|
104287
|
-
*
|
|
104288
|
-
* | 1. Containment Hierarchy | 2. Types Hierarchy | 3. Storeys Hierarchy |
|
|
104289
|
-
* |---|---|---|
|
|
104290
|
-
* | <img src="http://xeokit.io/img/docs/TreeViewPlugin/structureMode.png"> | <img src="http://xeokit.io/img/docs/TreeViewPlugin/typesMode.png"> | <img src="http://xeokit.io/img/docs/TreeViewPlugin/storeysMode.png"> |
|
|
104291
|
-
* <br>
|
|
104292
|
-
*
|
|
104293
|
-
* Let's create a TreeViewPlugin that groups nodes by their building stories and IFC types:
|
|
104294
|
-
*
|
|
104295
|
-
* ````javascript
|
|
104296
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104297
|
-
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
104298
|
-
* hierarchy: "stories"
|
|
104299
|
-
* });
|
|
104300
|
-
* ````
|
|
104301
|
-
*
|
|
104302
|
-
* ## Sorting Nodes
|
|
104303
|
-
*
|
|
104304
|
-
* TreeViewPlugin sorts its tree nodes by default. For a "storeys" hierarchy, it orders ````IfcBuildingStorey```` nodes
|
|
104305
|
-
* spatially, with the node for the highest story at the top, down to the lowest at the bottom.
|
|
104306
|
-
*
|
|
104307
|
-
* For all the hierarchy types ("containment", "classes" and "storeys"), TreeViewPlugin sorts the other node types
|
|
104308
|
-
* alphanumerically on their titles.
|
|
104309
|
-
*
|
|
104310
|
-
* If for some reason you need to prevent sorting, create your TreeViewPlugin with the option disabled, like so:
|
|
104311
|
-
*
|
|
104312
|
-
* ````javascript
|
|
104313
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104314
|
-
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
104315
|
-
* hierarchy: "stories",
|
|
104316
|
-
* sortNodes: false // <<------ Disable node sorting
|
|
104317
|
-
* });
|
|
104318
|
-
* ````
|
|
104319
|
-
*
|
|
104320
|
-
* Note that, for all hierarchy modes, node sorting is only done for each model at the time that it is added to the TreeViewPlugin, and will not
|
|
104321
|
-
* update dynamically if we later transform the {@link Entity}s corresponding to the nodes.
|
|
104322
|
-
*
|
|
104323
|
-
* ## Pruning empty nodes
|
|
104324
|
-
*
|
|
104325
|
-
* Sometimes a model contains subtrees of objects that don't have any geometry. These are models whose
|
|
104326
|
-
* {@link MetaModel} contains trees of {@link MetaObject}s that don't have any {@link Entity}s in the {@link Scene}.
|
|
104327
|
-
*
|
|
104328
|
-
* For these models, the tree view would contain nodes that don't do anything in the Scene when we interact with them,
|
|
104329
|
-
* which is undesirable.
|
|
104330
|
-
*
|
|
104331
|
-
* By default, TreeViewPlugin will not create nodes for those objects. However, we can override that behaviour if we want
|
|
104332
|
-
* to have nodes for those objects (perhaps for debugging the model):
|
|
104333
|
-
*
|
|
104334
|
-
* ````javascript
|
|
104335
|
-
* const treeView = new TreeViewPlugin(viewer, {
|
|
104336
|
-
* containerElement: document.getElementById("myTreeViewContainer"),
|
|
104337
|
-
* hierarchy: "stories",
|
|
104338
|
-
* pruneEmptyNodes: false // <<------ Create nodes for object subtrees without geometry
|
|
104339
|
-
* });
|
|
104340
|
-
* ````
|
|
104341
|
-
*
|
|
104342
|
-
* ## Context Menu
|
|
104343
|
-
*
|
|
104344
|
-
* TreeViewPlugin fires a "contextmenu" event whenever we right-click on a tree node.
|
|
104345
|
-
*
|
|
104346
|
-
* The event contains:
|
|
104347
|
-
*
|
|
104348
|
-
* * ````event```` - the original [contextmenu](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event) [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
|
|
104349
|
-
* * ````viewer```` - the {@link Viewer}
|
|
104350
|
-
* * ````treeViewPlugin```` - the TreeViewPlugin
|
|
104351
|
-
* * ````treeViewNode```` - the {@link TreeViewNode} representing the tree node
|
|
104352
|
-
*<br><br>
|
|
104353
|
-
*
|
|
104354
|
-
* Let's use {@link ContextMenu} to show a simple context menu for the node we clicked.
|
|
104355
|
-
*
|
|
104356
|
-
* [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/#ContextMenu_Canvas_TreeViewPlugin_Custom)]
|
|
104357
|
-
*
|
|
104358
|
-
* ````javascript
|
|
104359
|
-
* import {ContextMenu} from "../src/extras/ContextMenu/ContextMenu.js";
|
|
104360
|
-
*
|
|
104361
|
-
* const treeViewContextMenu = new ContextMenu({
|
|
104362
|
-
* items: [
|
|
104363
|
-
* [
|
|
104364
|
-
* [
|
|
104365
|
-
* {
|
|
104366
|
-
* title: "Hide",
|
|
104367
|
-
* doAction: function (context) {
|
|
104368
|
-
* context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
|
|
104369
|
-
* if (treeViewNode.objectId) {
|
|
104370
|
-
* const entity = context.viewer.scene.objects[treeViewNode.objectId];
|
|
104371
|
-
* if (entity) {
|
|
104372
|
-
* entity.visible = false;
|
|
104373
|
-
* }
|
|
104374
|
-
* }
|
|
104375
|
-
* });
|
|
104376
|
-
* }
|
|
104377
|
-
* },
|
|
104378
|
-
* {
|
|
104379
|
-
* title: "Hide all",
|
|
104380
|
-
* doAction: function (context) {
|
|
104381
|
-
* context.viewer.scene.setObjectsVisible(context.viewer.scene.visibleObjectIds, false);
|
|
104382
|
-
* }
|
|
104383
|
-
* }
|
|
104384
|
-
* ],
|
|
104385
|
-
* [
|
|
104386
|
-
* {
|
|
104387
|
-
* title: "Show",
|
|
104388
|
-
* doAction: function (context) {
|
|
104389
|
-
* context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
|
|
104390
|
-
* if (treeViewNode.objectId) {
|
|
104391
|
-
* const entity = context.viewer.scene.objects[treeViewNode.objectId];
|
|
104392
|
-
* if (entity) {
|
|
104393
|
-
* entity.visible = true;
|
|
104394
|
-
* entity.xrayed = false;
|
|
104395
|
-
* entity.selected = false;
|
|
104396
|
-
* }
|
|
104397
|
-
* }
|
|
104398
|
-
* });
|
|
104399
|
-
* }
|
|
104400
|
-
* },
|
|
104401
|
-
* {
|
|
104402
|
-
* title: "Show all",
|
|
104403
|
-
* doAction: function (context) {
|
|
104404
|
-
* const scene = context.viewer.scene;
|
|
104405
|
-
* scene.setObjectsVisible(scene.objectIds, true);
|
|
104406
|
-
* scene.setObjectsXRayed(scene.xrayedObjectIds, false);
|
|
104407
|
-
* scene.setObjectsSelected(scene.selectedObjectIds, false);
|
|
104408
|
-
* }
|
|
104409
|
-
* }
|
|
104410
|
-
* ]
|
|
104411
|
-
* ]
|
|
104412
|
-
* ]
|
|
104413
|
-
* });
|
|
104414
|
-
*
|
|
104415
|
-
* treeView.on("contextmenu", (e) => {
|
|
104416
|
-
*
|
|
104417
|
-
* const event = e.event; // MouseEvent
|
|
104418
|
-
* const viewer = e.viewer; // Viewer
|
|
104419
|
-
* const treeViewPlugin = e.treeViewPlugin; // TreeViewPlugin
|
|
104420
|
-
* const treeViewNode = e.treeViewNode; // TreeViewNode
|
|
104421
|
-
*
|
|
104422
|
-
* treeViewContextMenu.show(e.event.pageX, e.event.pageY);
|
|
104423
|
-
*
|
|
104424
|
-
* treeViewContextMenu.context = {
|
|
104425
|
-
* viewer: e.viewer,
|
|
104426
|
-
* treeViewPlugin: e.treeViewPlugin,
|
|
104427
|
-
* treeViewNode: e.treeViewNode
|
|
104428
|
-
* };
|
|
104429
|
-
* });
|
|
104430
|
-
* ````
|
|
104431
|
-
*
|
|
104432
|
-
* ## Clicking Node Titles
|
|
104433
|
-
*
|
|
104434
|
-
* TreeViewPlugin fires a "nodeTitleClicked" event whenever we left-click on a tree node.
|
|
104435
|
-
*
|
|
104436
|
-
* Like the "contextmenu" event, this event contains:
|
|
104437
|
-
*
|
|
104438
|
-
* * ````event```` - the original [click](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event) [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
|
|
104439
|
-
* * ````viewer```` - the {@link Viewer}
|
|
104440
|
-
* * ````treeViewPlugin```` - the TreeViewPlugin
|
|
104441
|
-
* * ````treeViewNode```` - the {@link TreeViewNode} representing the tree node
|
|
104442
|
-
*<br><br>
|
|
104443
|
-
*
|
|
104444
|
-
* Let's register a callback to isolate and fit-to-view the {@link Entity}(s) represented by the node. This callback is
|
|
104445
|
-
* going to X-ray all the other Entitys, fly the camera to fit the Entity(s) for the clicked node, then hide the other Entitys.
|
|
104446
|
-
*
|
|
104447
|
-
* [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/#ContextMenu_Canvas_TreeViewPlugin_Custom)]
|
|
104448
|
-
*
|
|
104449
|
-
* ````javascript
|
|
104450
|
-
* treeView.on("nodeTitleClicked", (e) => {
|
|
104451
|
-
* const scene = viewer.scene;
|
|
104452
|
-
* const objectIds = [];
|
|
104453
|
-
* e.treeViewPlugin.withNodeTree(e.treeViewNode, (treeViewNode) => {
|
|
104454
|
-
* if (treeViewNode.objectId) {
|
|
104455
|
-
* objectIds.push(treeViewNode.objectId);
|
|
104456
|
-
* }
|
|
104457
|
-
* });
|
|
104458
|
-
* scene.setObjectsXRayed(scene.objectIds, true);
|
|
104459
|
-
* scene.setObjectsVisible(scene.objectIds, true);
|
|
104460
|
-
* scene.setObjectsXRayed(objectIds, false);
|
|
104461
|
-
* viewer.cameraFlight.flyTo({
|
|
104462
|
-
* aabb: scene.getAABB(objectIds),
|
|
104463
|
-
* duration: 0.5
|
|
104464
|
-
* }, () => {
|
|
104465
|
-
* setTimeout(function () {
|
|
104466
|
-
* scene.setObjectsVisible(scene.xrayedObjectIds, false);
|
|
104467
|
-
* scene.setObjectsXRayed(scene.xrayedObjectIds, false);
|
|
104468
|
-
* }, 500);
|
|
104469
|
-
* });
|
|
104470
|
-
* });
|
|
104471
|
-
* ````
|
|
104472
|
-
*
|
|
104473
|
-
* To make the cursor change to a pointer when we hover over the node titles, and also to make the titles change to blue, we'll also define this CSS for the ````<span>```` elements
|
|
104474
|
-
* that represent the titles of our TreeViewPlugin nodes:
|
|
104475
|
-
*
|
|
104476
|
-
* ````css
|
|
104477
|
-
* #treeViewContainer ul li span:hover {
|
|
104478
|
-
* color: blue;
|
|
104479
|
-
* cursor: pointer;
|
|
104480
|
-
* }
|
|
104481
|
-
* ````
|
|
104482
|
-
*
|
|
104483
|
-
* @class TreeViewPlugin
|
|
104484
|
-
*/
|
|
104485
|
-
class TreeViewPlugin extends Plugin {
|
|
104486
|
-
|
|
104487
|
-
/**
|
|
104488
|
-
* @constructor
|
|
104489
|
-
*
|
|
104490
|
-
* @param {Viewer} viewer The Viewer.
|
|
104491
|
-
* @param {*} cfg Plugin configuration.
|
|
104492
|
-
* @param {HTMLElement} cfg.containerElement DOM element to contain the TreeViewPlugin.
|
|
104493
|
-
* @param {Boolean} [cfg.autoAddModels=true] When ````true```` (default), will automatically add each model as it's created. Set this ````false```` if you want to manually add models using {@link TreeViewPlugin#addModel} instead.
|
|
104494
|
-
* @param {Number} [cfg.autoExpandDepth] Optional depth to which to initially expand the tree.
|
|
104495
|
-
* @param {String} [cfg.hierarchy="containment"] How to organize the tree nodes: "containment", "storeys" or "types". See the class documentation for details.
|
|
104496
|
-
* @param {Boolean} [cfg.sortNodes=true] When true, will sort the children of each node. For a "storeys" hierarchy, the
|
|
104497
|
-
* ````IfcBuildingStorey```` nodes will be ordered spatially, from the highest storey down to the lowest, on the
|
|
104498
|
-
* vertical World axis. For all hierarchy types, other node types will be ordered in the ascending alphanumeric order of their titles.
|
|
104499
|
-
* @param {Boolean} [cfg.pruneEmptyNodes=true] When true, will not contain nodes that don't have content in the {@link Scene}. These are nodes whose {@link MetaObject}s don't have {@link Entity}s.
|
|
104500
|
-
*/
|
|
104501
|
-
constructor(viewer, cfg = {}) {
|
|
104502
|
-
|
|
104503
|
-
super("TreeViewPlugin", viewer);
|
|
104504
|
-
|
|
104505
|
-
if (!cfg.containerElement) {
|
|
104506
|
-
this.error("Config expected: containerElement");
|
|
104507
|
-
return;
|
|
104508
|
-
}
|
|
104509
|
-
|
|
104510
|
-
this._containerElement = cfg.containerElement;
|
|
104511
|
-
this._modelTreeViews = {};
|
|
104512
|
-
this._autoAddModels = (cfg.autoAddModels !== false);
|
|
104513
|
-
this._autoExpandDepth = (cfg.autoExpandDepth || 0);
|
|
104514
|
-
this._sortNodes = (cfg.sortNodes !== false);
|
|
104515
|
-
this._pruneEmptyNodes = (cfg.pruneEmptyNodes !== false);
|
|
104516
|
-
|
|
104517
|
-
if (this._autoAddModels) {
|
|
104518
|
-
const modelIds = Object.keys(this.viewer.metaScene.metaModels);
|
|
104519
|
-
for (let i = 0, len = modelIds.length; i < len; i++) {
|
|
104520
|
-
const modelId = modelIds[i];
|
|
104521
|
-
this.addModel(modelId);
|
|
104522
|
-
}
|
|
104523
|
-
this.viewer.scene.on("modelLoaded", (modelId) => {
|
|
104524
|
-
if (this.viewer.metaScene.metaModels[modelId]) {
|
|
104525
|
-
this.addModel(modelId);
|
|
104526
|
-
}
|
|
104527
|
-
});
|
|
104528
|
-
}
|
|
104529
|
-
|
|
104530
|
-
this.hierarchy = cfg.hierarchy;
|
|
104531
|
-
}
|
|
104532
|
-
|
|
104533
|
-
/**
|
|
104534
|
-
* Returns the map of {@link ModelTreeView}s.
|
|
104535
|
-
*
|
|
104536
|
-
* Each ModelTreeView is mapped to the ID of its model.
|
|
104537
|
-
*
|
|
104538
|
-
* @return {*|{}}
|
|
104539
|
-
*/
|
|
104540
|
-
get modelTreeViews() {
|
|
104541
|
-
return this._modelTreeViews;
|
|
104542
|
-
}
|
|
104543
|
-
|
|
104544
|
-
/**
|
|
104545
|
-
* Sets how the nodes are organized within this tree view.
|
|
104546
|
-
*
|
|
104547
|
-
* Accepted values are:
|
|
104548
|
-
*
|
|
104549
|
-
* * "containment" - organizes the nodes to indicate the containment hierarchy of the IFC objects.
|
|
104550
|
-
* * "types" - groups the nodes within their IFC types.
|
|
104551
|
-
* * "storeys" - groups the nodes within ````IfcBuildingStoreys```` and sub-groups them by their IFC types.
|
|
104552
|
-
*
|
|
104553
|
-
* <br>
|
|
104554
|
-
* This can be updated dynamically.
|
|
104555
|
-
*
|
|
104556
|
-
* Default value is "containment".
|
|
104557
|
-
*
|
|
104558
|
-
* @type {String}
|
|
104559
|
-
*/
|
|
104560
|
-
set hierarchy(hierarchy) {
|
|
104561
|
-
hierarchy = hierarchy || "containment";
|
|
104562
|
-
if (hierarchy !== "containment" && hierarchy !== "storeys" && hierarchy !== "types") {
|
|
104563
|
-
this.error("Unsupported value for `hierarchy' - defaulting to 'containment'");
|
|
104564
|
-
hierarchy = "containment";
|
|
104565
|
-
}
|
|
104566
|
-
this._hierarchy = hierarchy;
|
|
104567
|
-
for (let modelId in this._modelTreeViews) {
|
|
104568
|
-
if (this._modelTreeViews.hasOwnProperty(modelId)) {
|
|
104569
|
-
this._modelTreeViews[modelId].setHierarchy(this._hierarchy);
|
|
104570
|
-
}
|
|
104571
|
-
}
|
|
104572
|
-
}
|
|
104573
|
-
|
|
104574
|
-
/**
|
|
104575
|
-
* Gets how the nodes are organized within this tree view.
|
|
104576
|
-
*
|
|
104577
|
-
* @type {String}
|
|
104578
|
-
*/
|
|
104579
|
-
get hierarchy() {
|
|
104580
|
-
return this._hierarchy;
|
|
104581
|
-
}
|
|
104582
|
-
|
|
104583
|
-
/**
|
|
104584
|
-
* Adds a model to this tree view.
|
|
104585
|
-
*
|
|
104586
|
-
* The model will be automatically removed when destroyed.
|
|
104587
|
-
*
|
|
104588
|
-
* To automatically add each model as it's created, instead of manually calling this method each time,
|
|
104589
|
-
* provide a ````autoAddModels: true```` to the TreeViewPlugin constructor.
|
|
104590
|
-
*
|
|
104591
|
-
* @param {String} modelId ID of a model {@link Entity} in {@link Scene#models}.
|
|
104592
|
-
* @param {Object} [options] Options for model in the tree view.
|
|
104593
|
-
* @param {String} [options.rootName] Optional display name for the root node. Ordinary, for "containment"
|
|
104594
|
-
* and "storeys" hierarchy types, the tree would derive the root node name from the model's "IfcProject" element
|
|
104595
|
-
* name. This option allows to override that name when it is not suitable as a display name.
|
|
104596
|
-
* @returns {ModelTreeView} ModelTreeView for the newly-added model. If this method succeeded in adding the model,
|
|
104597
|
-
* then {@link ModelTreeView#valid} will equal ````true````. Otherwise, that property will be ````false````
|
|
104598
|
-
* and {@link ModelTreeView#errors} will contain error messages.
|
|
104599
|
-
*/
|
|
104600
|
-
addModel(modelId, options = {}) {
|
|
104601
|
-
if (!this._containerElement) {
|
|
104602
|
-
return;
|
|
104603
|
-
}
|
|
104604
|
-
const model = this.viewer.scene.models[modelId];
|
|
104605
|
-
if (!model) {
|
|
104606
|
-
throw "Model not found: " + modelId;
|
|
104607
|
-
}
|
|
104608
|
-
const metaModel = this.viewer.metaScene.metaModels[modelId];
|
|
104609
|
-
if (!metaModel) {
|
|
104610
|
-
this.error("MetaModel not found: " + modelId);
|
|
104611
|
-
return;
|
|
104612
|
-
}
|
|
104613
|
-
if (this._modelTreeViews[modelId]) {
|
|
104614
|
-
this.warn("Model already added: " + modelId);
|
|
104615
|
-
return;
|
|
104616
|
-
}
|
|
104617
|
-
const modelTreeView = new ModelTreeView(this.viewer, this, model, metaModel, {
|
|
104618
|
-
containerElement: this._containerElement,
|
|
104619
|
-
autoExpandDepth: this._autoExpandDepth,
|
|
104620
|
-
hierarchy: this._hierarchy,
|
|
104621
|
-
sortNodes: this._sortNodes,
|
|
104622
|
-
pruneEmptyNodes: this._pruneEmptyNodes,
|
|
104623
|
-
rootName: options.rootName
|
|
104624
|
-
});
|
|
104625
|
-
this._modelTreeViews[modelId] = modelTreeView;
|
|
104626
|
-
model.on("destroyed", () => {
|
|
104627
|
-
this.removeModel(model.id);
|
|
104628
|
-
});
|
|
104629
|
-
return modelTreeView;
|
|
104630
|
-
}
|
|
104631
|
-
|
|
104632
|
-
/**
|
|
104633
|
-
* Removes a model from this tree view.
|
|
104634
|
-
*
|
|
104635
|
-
* Does nothing if model not currently in tree view.
|
|
104636
|
-
*
|
|
104637
|
-
* @param {String} modelId ID of a model {@link Entity} in {@link Scene#models}.
|
|
104638
|
-
*/
|
|
104639
|
-
removeModel(modelId) {
|
|
104640
|
-
if (!this._containerElement) {
|
|
104641
|
-
return;
|
|
104642
|
-
}
|
|
104643
|
-
const modelTreeView = this._modelTreeViews[modelId];
|
|
104644
|
-
if (!modelTreeView) {
|
|
104645
|
-
return;
|
|
104646
|
-
}
|
|
104647
|
-
modelTreeView.destroy();
|
|
104648
|
-
delete this._modelTreeViews[modelId];
|
|
104649
|
-
}
|
|
104650
|
-
|
|
104651
|
-
/**
|
|
104652
|
-
* Collapses all trees within this tree view.
|
|
104653
|
-
*/
|
|
104654
|
-
collapse() {
|
|
104655
|
-
for (let modelId in this._modelTreeViews) {
|
|
104656
|
-
if (this._modelTreeViews.hasOwnProperty(modelId)) {
|
|
104657
|
-
const modelTreeView = this._modelTreeViews[modelId];
|
|
104658
|
-
modelTreeView.collapse();
|
|
104659
|
-
}
|
|
104660
|
-
}
|
|
104661
|
-
}
|
|
104662
|
-
|
|
104663
|
-
/**
|
|
104664
|
-
* Highlights the tree view node that represents the given object {@link Entity}.
|
|
104665
|
-
*
|
|
104666
|
-
* This causes the tree view to collapse, then expand to reveal the node, then highlight the node.
|
|
104667
|
-
*
|
|
104668
|
-
* If a node is previously highlighted, de-highlights that node and collapses the tree first.
|
|
104669
|
-
*
|
|
104670
|
-
* Note that if the TreeViewPlugin was configured with ````pruneEmptyNodes: true```` (default configuration), then the
|
|
104671
|
-
* node won't exist in the tree if it has no Entitys in the {@link Scene}. in that case, nothing will happen.
|
|
104672
|
-
*
|
|
104673
|
-
* Within the DOM, the node is represented by an ````<li>```` element. This method will add a ````.highlighted-node```` class to
|
|
104674
|
-
* the element to make it appear highlighted, removing that class when de-highlighting it again. See the CSS rules
|
|
104675
|
-
* in the TreeViewPlugin examples for an example of that class.
|
|
104676
|
-
*
|
|
104677
|
-
* @param {String} objectId ID of the {@link Entity}.
|
|
104678
|
-
*/
|
|
104679
|
-
showNode(objectId) {
|
|
104680
|
-
this.unShowNode();
|
|
104681
|
-
const metaObject = this.viewer.metaScene.metaObjects[objectId];
|
|
104682
|
-
if (!metaObject) {
|
|
104683
|
-
this.error("MetaObject not found: " + objectId);
|
|
104684
|
-
return;
|
|
104685
|
-
}
|
|
104686
|
-
const metaModel = metaObject.metaModel;
|
|
104687
|
-
const modelId = metaModel.id;
|
|
104688
|
-
const modelTreeView = this._modelTreeViews[modelId];
|
|
104689
|
-
if (!modelTreeView) {
|
|
104690
|
-
this.error("Object not in this TreeView: " + objectId);
|
|
104691
|
-
return;
|
|
104692
|
-
}
|
|
104693
|
-
modelTreeView.showNode(objectId);
|
|
104694
|
-
}
|
|
104695
|
-
|
|
104696
|
-
/**
|
|
104697
|
-
* De-highlights the node previously shown with {@link TreeViewPlugin#showNode}.
|
|
104698
|
-
*
|
|
104699
|
-
* Does nothing if no node is currently shown.
|
|
104700
|
-
*
|
|
104701
|
-
* If the node is currently scrolled into view, keeps the node in view.
|
|
104702
|
-
*/
|
|
104703
|
-
unShowNode() {
|
|
104704
|
-
for (let modelId in this._modelTreeViews) {
|
|
104705
|
-
if (this._modelTreeViews.hasOwnProperty(modelId)) {
|
|
104706
|
-
const modelTreeView = this._modelTreeViews[modelId];
|
|
104707
|
-
modelTreeView.unShowNode();
|
|
104708
|
-
}
|
|
104709
|
-
}
|
|
104710
|
-
}
|
|
104711
|
-
|
|
104712
|
-
/**
|
|
104713
|
-
* Expands the tree to the given depth.
|
|
104714
|
-
*
|
|
104715
|
-
* Collapses the tree first.
|
|
104716
|
-
*
|
|
104717
|
-
* @param {Number} depth Depth to expand to.
|
|
104718
|
-
*/
|
|
104719
|
-
expandToDepth(depth) {
|
|
104720
|
-
for (let modelId in this._modelTreeViews) {
|
|
104721
|
-
if (this._modelTreeViews.hasOwnProperty(modelId)) {
|
|
104722
|
-
const modelTreeView = this._modelTreeViews[modelId];
|
|
104723
|
-
modelTreeView.collapse();
|
|
104724
|
-
modelTreeView.expandToDepth(depth);
|
|
104725
|
-
}
|
|
104726
|
-
}
|
|
104727
|
-
}
|
|
104728
|
-
|
|
104729
|
-
/**
|
|
104730
|
-
* Iterates over a subtree of the tree view's {@link TreeViewNode}s, calling the given callback for each
|
|
104731
|
-
* node in depth-first pre-order.
|
|
104732
|
-
*
|
|
104733
|
-
* @param {TreeViewNode} node Root of the subtree.
|
|
104734
|
-
* @param {Function} callback Callback called at each {@link TreeViewNode}, with the TreeViewNode given as the argument.
|
|
104735
|
-
*/
|
|
104736
|
-
withNodeTree(node, callback) {
|
|
104737
|
-
callback(node);
|
|
104738
|
-
const children = node.children;
|
|
104739
|
-
if (!children) {
|
|
104740
|
-
return;
|
|
104741
|
-
}
|
|
104742
|
-
for (let i = 0, len = children.length; i < len; i++) {
|
|
104743
|
-
this.withNodeTree(children[i], callback);
|
|
104744
|
-
}
|
|
104745
|
-
}
|
|
104746
|
-
|
|
104747
|
-
/**
|
|
104748
|
-
* Destroys this TreeViewPlugin.
|
|
104749
|
-
*/
|
|
104750
|
-
destroy() {
|
|
104751
|
-
if (!this._containerElement) {
|
|
104752
|
-
return;
|
|
104753
|
-
}
|
|
104754
|
-
for (let modelId in this._modelTreeViews) {
|
|
104755
|
-
if (this._modelTreeViews.hasOwnProperty(modelId)) {
|
|
104756
|
-
this._modelTreeViews[modelId].destroy();
|
|
104757
|
-
}
|
|
104758
|
-
}
|
|
104759
|
-
this._modelTreeViews = {};
|
|
104760
|
-
super.destroy();
|
|
104593
|
+
switchElement.removeEventListener('click', this._switchCollapseHandler);
|
|
104594
|
+
switchElement.addEventListener('click', this._switchExpandHandler);
|
|
104761
104595
|
}
|
|
104762
104596
|
}
|
|
104763
104597
|
|
|
@@ -116460,7 +116294,7 @@ class XKTLoaderPlugin extends Plugin {
|
|
|
116460
116294
|
}
|
|
116461
116295
|
|
|
116462
116296
|
options.excludeUnclassifiedObjects = (params.excludeUnclassifiedObjects !== undefined) ? (!!params.excludeUnclassifiedObjects) : this._excludeUnclassifiedObjects;
|
|
116463
|
-
options.globalizeObjectIds = (params.globalizeObjectIds !== undefined) ? (!!params.globalizeObjectIds) : this._globalizeObjectIds;
|
|
116297
|
+
options.globalizeObjectIds = (params.globalizeObjectIds !== undefined && params.globalizeObjectIds !== null) ? (!!params.globalizeObjectIds) : this._globalizeObjectIds;
|
|
116464
116298
|
|
|
116465
116299
|
if (params.metaModelSrc || params.metaModelData) {
|
|
116466
116300
|
|
|
@@ -116469,7 +116303,7 @@ class XKTLoaderPlugin extends Plugin {
|
|
|
116469
116303
|
const metaModel = this.viewer.metaScene.createMetaModel(modelId, metaModelData, {
|
|
116470
116304
|
includeTypes: includeTypes,
|
|
116471
116305
|
excludeTypes: excludeTypes,
|
|
116472
|
-
globalizeObjectIds:
|
|
116306
|
+
globalizeObjectIds: options.globalizeObjectIds
|
|
116473
116307
|
});
|
|
116474
116308
|
|
|
116475
116309
|
if (!metaModel) {
|