import {TreeViewPlugin, math} from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js"; import {Controller} from "../Controller.js"; import {TreeViewContextMenu} from "../contextMenus/TreeViewContextMenu.js"; import { DEFAULT_DOUBLE_CLICK_THRESHOLD, focusTreeNodeInViewer } from "./treeViewFocusHelper.js"; const tempVec3 = math.vec3(); /** @private */ class StoreysExplorer extends Controller { constructor(parent, cfg = {}) { super(parent); if (!cfg.storeysTabElement) { throw "Missing config: storeysTabElement"; } if (!cfg.showAllStoreysButtonElement) { throw "Missing config: showAllStoreysButtonElement"; } if (!cfg.hideAllStoreysButtonElement) { throw "Missing config: hideAllStoreysButtonElement"; } if (!cfg.storeysElement) { throw "Missing config: storeysElement"; } this._containerElement = cfg.containerElement; this._storeysTabElement = cfg.storeysTabElement; this._showAllStoreysButtonElement = cfg.showAllStoreysButtonElement; this._hideAllStoreysButtonElement = cfg.hideAllStoreysButtonElement; this._storeysTabButtonElement = this._storeysTabElement.querySelector(".xeokit-tab-btn"); if (!this._storeysTabButtonElement) { throw "Missing DOM element: .xeokit-tab-btn"; } const storeysElement = cfg.storeysElement; this._treeView = new TreeViewPlugin(this.viewer, { containerElement: storeysElement, autoAddModels: false, hierarchy: "storeys", autoExpandDepth: 1 }); this._treeViewContextMenu = new TreeViewContextMenu(this.bimViewer, { hideOnAction: true, enableMeasurements: cfg.enableMeasurements, parentNode: cfg.containerElement }); this._lastNodeClickTime = 0; this._lastNodeClickId = null; this._doubleClickThreshold = this.viewer.doubleClickTimeFrame || DEFAULT_DOUBLE_CLICK_THRESHOLD; this._treeView.on("contextmenu", (e) => { this._treeViewContextMenu.context = { bimViewer: this.bimViewer, viewer: e.viewer, treeViewPlugin: e.treeViewPlugin, treeViewNode: e.treeViewNode, pruneEmptyNodes: true }; this._treeViewContextMenu.show(e.event.pageX, e.event.pageY); }); this._treeView.on("nodeTitleClicked", (e) => { const now = (typeof performance !== "undefined" && performance.now) ? performance.now() : Date.now(); const nodeId = e.treeViewNode.nodeId || e.treeViewNode.id; const clickCount = e.event && e.event.detail ? e.event.detail : 1; const isSameNode = (this._lastNodeClickId === nodeId); const withinThreshold = isSameNode && ((now - this._lastNodeClickTime) <= this._doubleClickThreshold); const isDoubleClick = (clickCount >= 2) || withinThreshold; this._lastNodeClickTime = now; this._lastNodeClickId = nodeId; if (isDoubleClick) { console.log("[StoreysExplorer] double click", { nodeId: nodeId, title: e.treeViewNode.title || e.treeViewNode.name }); this._onTreeNodeTitleDoubleClicked(e); return; } this._onTreeNodeTitleSingleClicked(e); }); this._onModelLoaded = this.viewer.scene.on("modelLoaded", (modelId) =>{ const modelInfo = this.bimViewer._modelsExplorer.getModelInfo(modelId); if (!modelInfo) { return; } this._treeView.addModel(modelId, { rootName: modelInfo.name }); }); this._onModelUnloaded = this.viewer.scene.on("modelUnloaded", (modelId) => { if (this.viewer.metaScene.metaModels[modelId]) { this._treeView.removeModel(modelId); } }); this.bimViewer.on("reset", () => { this._treeView.collapse(); this._treeView.expandToDepth(1); }); } setEnabled(enabled) { if (!enabled) { this._storeysTabButtonElement.classList.add("disabled"); this._showAllStoreysButtonElement.classList.add("disabled"); this._hideAllStoreysButtonElement.classList.add("disabled"); } else { this._storeysTabButtonElement.classList.remove("disabled"); this._showAllStoreysButtonElement.classList.remove("disabled"); this._hideAllStoreysButtonElement.classList.remove("disabled"); } } expandTreeViewToDepth(depth) { this._treeView.expandToDepth(depth); } showNodeInTreeView(objectId) { this._treeView.collapse(); this._treeView.showNode(objectId); } unShowNodeInTreeView() { this._treeView.unShowNode(); } _onTreeNodeTitleSingleClicked(e) { const scene = this.viewer.scene; const objectIds = []; e.treeViewPlugin.withNodeTree(e.treeViewNode, (treeViewNode) => { if (treeViewNode.objectId) { objectIds.push(treeViewNode.objectId); } }); const checked = e.treeViewNode.checked; if (checked) { scene.setObjectsXRayed(objectIds, false); scene.setObjectsVisible(objectIds, false); scene.setObjectsPickable(objectIds, true); } else { scene.setObjectsXRayed(objectIds, false); scene.setObjectsVisible(objectIds, true); scene.setObjectsPickable(objectIds, true); } } _onTreeNodeTitleDoubleClicked(e) { this._focusTreeNodeInViewer(e); } _focusTreeNodeInViewer(e) { focusTreeNodeInViewer(this.bimViewer, e.treeViewPlugin, e.treeViewNode); } selectStorey(storeyObjectId, done) { const metaScene = this.viewer.metaScene; const storeyMetaObject = metaScene.metaObjects[storeyObjectId]; if (!storeyMetaObject) { this.error("selectStorey() - object is not found: '" + storeyObjectId + "'"); return; } if (storeyMetaObject.type !== "IfcBuildingStorey") { this.error("selectStorey() - object is not found: '" + storeyObjectId + "'"); return; } const objectIds = storeyMetaObject.getObjectIDsInSubtree(); this._selectObjects(objectIds, done); } _selectObjects(objectIds, done) { const scene = this.viewer.scene; const aabb = scene.getAABB(objectIds); this.viewer.cameraControl.pivotPos = math.getAABB3Center(aabb, tempVec3); if (done) { scene.setObjectsXRayed(scene.objectIds, true); scene.setObjectsVisible(scene.objectIds, true); scene.setObjectsPickable(scene.objectIds, false); scene.setObjectsSelected(scene.selectedObjectIds, false); scene.setObjectsXRayed(objectIds, false); scene.setObjectsVisible(objectIds, true); scene.setObjectsPickable(objectIds, true); this.viewer.cameraFlight.flyTo({ aabb: aabb }, () => { setTimeout(function () { scene.setObjectsVisible(scene.xrayedObjectIds, false); scene.setObjectsXRayed(scene.xrayedObjectIds, false); }, 500); done(); }); } else { scene.setObjectsVisible(scene.objectIds, false); scene.setObjectsPickable(scene.xrayedObjectIds, true); scene.setObjectsXRayed(scene.xrayedObjectIds, false); scene.setObjectsSelected(scene.selectedObjectIds, false); scene.setObjectsVisible(objectIds, true); this.viewer.cameraFlight.jumpTo({ aabb: aabb }); } } destroy() { super.destroy(); this._treeView.destroy(); this._treeViewContextMenu.destroy(); this.viewer.scene.off(this._onModelLoaded); this.viewer.scene.off(this._onModelUnloaded); } } export {StoreysExplorer};