Reference Source

src/BIMViewer.js

  1. import {BCFViewpointsPlugin, FastNavPlugin, math, stats, Viewer,} from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
  2.  
  3. import {Controller} from "./Controller.js";
  4. import {BusyModal} from "./BusyModal.js";
  5. import {ResetAction} from "./toolbar/ResetAction.js";
  6. import {FitAction} from "./toolbar/FitAction.js";
  7. import {FirstPersonMode} from "./toolbar/FirstPersonMode.js";
  8. import {HideTool} from "./toolbar/HideTool.js";
  9. import {SelectionTool} from "./toolbar/SelectionTool.js";
  10. import {ShowSpacesMode} from "./toolbar/ShowSpacesMode.js";
  11. import {QueryTool} from "./toolbar/QueryTool.js";
  12. import {SectionTool} from "./toolbar/SectionTool.js";
  13. import {NavCubeMode} from "./toolbar/NavCubeMode.js";
  14.  
  15. import {ModelsExplorer} from "./explorer/ModelsExplorer.js";
  16. import {ObjectsExplorer} from "./explorer/ObjectsExplorer.js";
  17. import {ClassesExplorer} from "./explorer/ClassesExplorer.js";
  18. import {StoreysExplorer} from "./explorer/StoreysExplorer.js";
  19.  
  20. import {ThreeDMode} from "./toolbar/ThreeDMode.js";
  21. import {ObjectContextMenu} from "./contextMenus/ObjectContextMenu.js";
  22. import {CanvasContextMenu} from "./contextMenus/CanvasContextMenu.js";
  23. import {OrthoMode} from "./toolbar/OrthoMode.js";
  24. import {PropertiesInspector} from "./inspector/PropertiesInspector.js";
  25. import {ObjectsKdTree3} from "./collision/ObjectsKdTree3.js";
  26. import {MarqueeSelectionTool} from "./toolbar/MarqueeSelectionTool.js";
  27.  
  28.  
  29. const hideEdgesMinDrawCount = 5; // FastNavPlugin enables dynamic edges when xeokit's per-frame draw count drops below this
  30. const scaleCanvasResolutionMinDrawCount = 1000; // FastNavPlugin switches to low-res canvas when xeokit's per-frame draw count rises above this
  31.  
  32. function createExplorerTemplate(cfg) {
  33. const explorerTemplate = `<div class="xeokit-tabs">
  34. <div class="xeokit-tab xeokit-modelsTab">
  35. <a class="xeokit-i18n xeokit-tab-btn" href="#" data-xeokit-i18n="modelsExplorer.title">Models</a>
  36. <div class="xeokit-tab-content">
  37. <div class="xeokit-btn-group">
  38. <button type="button" class="xeokit-i18n xeokit-loadAllModels xeokit-btn disabled" data-xeokit-i18n="modelsExplorer.loadAll" data-xeokit-i18ntip="modelsExplorer.loadAllTip" data-tippy-content="Load all models">Load all</button>
  39. <button type="button" class="xeokit-i18n xeokit-unloadAllModels xeokit-btn disabled" data-xeokit-i18n="modelsExplorer.unloadAll" data-xeokit-i18ntip="modelsExplorer.unloadAllTip" data-tippy-content="Unload all models">Unload all</button>` +
  40. (cfg.enableEditModels ? `<button type="button" class="xeokit-i18n xeokit-addModel xeokit-btn disabled" data-xeokit-i18n="modelsExplorer.add" data-xeokit-i18ntip="modelsExplorer.addTip" data-tippy-content="Add model">Add</button>` : ``) + `</div>
  41. <div class="xeokit-models" ></div>
  42. </div>
  43. </div>
  44. <div class="xeokit-tab xeokit-objectsTab">
  45. <a class="xeokit-i18n xeokit-tab-btn disabled" href="#" data-xeokit-i18n="objectsExplorer.title">Objects</a>
  46. <div class="xeokit-tab-content">
  47. <div class="xeokit-btn-group">
  48. <button type="button" class="xeokit-i18n xeokit-showAllObjects xeokit-btn disabled" data-xeokit-i18n="objectsExplorer.showAll" data-xeokit-i18ntip="objectsExplorer.showAllTip" data-tippy-content="Show all objects">Show all</button>
  49. <button type="button" class="xeokit-i18n xeokit-hideAllObjects xeokit-btn disabled" data-xeokit-i18n="objectsExplorer.hideAll" data-xeokit-i18ntip="objectsExplorer.hideAllTip" data-tippy-content="Hide all objects">Hide all</button>
  50. </div>
  51. <div class="xeokit-objects xeokit-tree-panel" ></div>
  52. </div>
  53. </div>
  54. <div class="xeokit-i18n xeokit-tab xeokit-classesTab">
  55. <a class="xeokit-i18n xeokit-tab-btn disabled" href="#" data-xeokit-i18n="classesExplorer.title">Classes</a>
  56. <div class="xeokit-tab-content">
  57. <div class="xeokit-btn-group">
  58. <button type="button" class="xeokit-i18n xeokit-showAllClasses xeokit-btn disabled" data-xeokit-i18n="classesExplorer.showAll" data-xeokit-i18ntip="classesExplorer.hideAllTip" data-tippy-content="Show all classes">Show all</button>
  59. <button type="button" class="xeokit-i18n xeokit-hideAllClasses xeokit-btn disabled" data-xeokit-i18n="classesExplorer.hideAll" data-xeokit-i18ntip="classesExplorer.hideAllTip" data-tippy-content="Hide all classes">Hide all</button>
  60. </div>
  61. <div class="xeokit-classes xeokit-tree-panel" ></div>
  62. </div>
  63. </div>
  64. <div class="xeokit-tab xeokit-storeysTab">
  65. <a class="xeokit-i18n xeokit-tab-btn disabled" href="#" data-xeokit-i18n="storeysExplorer.title">Storeys</a>
  66. <div class="xeokit-tab-content">
  67. <div class="xeokit-btn-group">
  68. <button type="button" class="xeokit-i18n xeokit-showAllStoreys xeokit-btn disabled" data-xeokit-i18n="storeysExplorer.showAll" data-xeokit-i18ntip="storeysExplorer.showAllTip" data-tippy-content="Show all storeys">Show all</button>
  69. <button type="button" class="xeokit-i18n xeokit-hideAllStoreys xeokit-btn disabled" data-xeokit-i18n="storeysExplorer.hideAll" data-xeokit-i18ntip="storeysExplorer.hideAllTip" data-tippy-content="Hide all storeys">Hide all</button>
  70. </div>
  71. <div class="xeokit-storeys xeokit-tree-panel"></div>
  72. </div>
  73. </div>
  74. </div>`;
  75. return explorerTemplate;
  76. }
  77.  
  78. function createToolbarTemplate() {
  79. const toolbarTemplate = `<div class="xeokit-toolbar">
  80. <!-- Reset button -->
  81. <div class="xeokit-btn-group">
  82. <button type="button" class="xeokit-i18n xeokit-reset xeokit-btn fa fa-home fa-2x disabled" data-xeokit-i18ntip="toolbar.resetViewTip" data-tippy-content="Reset view"></button>
  83. </div>
  84. <div class="xeokit-btn-group" role="group">
  85. <!-- 3D Mode button -->
  86. <button type="button" class="xeokit-i18n xeokit-threeD xeokit-btn fa fa-cube fa-2x disabled" data-xeokit-i18ntip="toolbar.toggle2d3dTip" data-tippy-content="Toggle 2D/3D"></button>
  87. <!-- Perspective/Ortho Mode button -->
  88. <button type="button" class="xeokit-i18n xeokit-ortho xeokit-btn fa fa-th fa-2x disabled" data-xeokit-i18ntip="toolbar.togglePerspectiveTip" data-tippy-content="Toggle Perspective/Ortho"></button>
  89. <!-- Fit button -->
  90. <button type="button" class="xeokit-i18n xeokit-fit xeokit-btn fa fa-crop fa-2x disabled" data-xeokit-i18ntip="toolbar.viewFitTip" data-tippy-content="View fit"></button>
  91. <!-- First Person mode button -->
  92. <button type="button" class="xeokit-i18n xeokit-firstPerson xeokit-btn fa fa-male fa-2x disabled" data-xeokit-i18ntip="toolbar.firstPersonTip" data-tippy-content="Toggle first-person mode"></button>
  93. <!-- Show/hide IFCSpaces -->
  94. <button type="button" class="xeokit-i18n xeokit-showSpaces xeokit-btn fab fa-codepen fa-2x disabled" data-xeokit-i18ntip="toolbar.showSpacesTip" data-tippy-content="Show IFCSpaces"></button>
  95. </div>
  96. <!-- Tools button group -->
  97. <div class="xeokit-btn-group" role="group">
  98. <!-- Hide tool button -->
  99. <button type="button" class="xeokit-i18n xeokit-hide xeokit-btn fa fa-eraser fa-2x disabled" data-xeokit-i18ntip="toolbar.hideObjectsTip" data-tippy-content="Hide objects"></button>
  100. <!-- Select tool button -->
  101. <button type="button" class="xeokit-i18n xeokit-select xeokit-btn fa fa-mouse-pointer fa-2x disabled" data-xeokit-i18ntip="toolbar.selectObjectsTip" data-tippy-content="Select objects"></button>
  102. <!-- Marquee select tool button -->
  103. <button type="button" class="xeokit-i18n xeokit-marquee xeokit-btn fas fa-object-group fa-2x disabled" data-xeokit-i18ntip="toolbar.marqueeSelectTip" data-tippy-content="Marquee select objects"></button>
  104. <!-- section tool button -->
  105. <button type="button" class="xeokit-i18n xeokit-section xeokit-btn fa fa-cut fa-2x disabled" data-xeokit-i18ntip="toolbar.sliceObjectsTip" data-tippy-content="Slice objects">
  106. <div class="xeokit-i18n xeokit-section-menu-button disabled" data-xeokit-i18ntip="toolbar.slicesMenuTip" data-tippy-content="Slices menu">
  107. <span class="xeokit-arrow-down xeokit-section-menu-button-arrow"></span>
  108. </div>
  109. <div class="xeokit-i18n xeokit-section-counter" data-xeokit-i18ntip="toolbar.numSlicesTip" data-tippy-content="Number of existing slices"></div>
  110. </button>
  111. </div>
  112. </div>`;
  113. return toolbarTemplate;
  114. }
  115.  
  116. function createInspectorTemplate() {
  117. const inspectorTemplate = `<div class="xeokit-tabs">
  118. <div class="xeokit-tab xeokit-propertiesTab">
  119. <a class="xeokit-i18n xeokit-tab-btn disabled" href="#" data-xeokit-i18n="propertiesInspector.title">Properties</a>
  120. <div class="xeokit-tab-content">
  121. <div class="xeokit-properties"></div>
  122. </div>
  123. </div>
  124. </div>`;
  125. return inspectorTemplate;
  126. }
  127.  
  128. function initTabs(containerElement) {
  129.  
  130. const tabsClass = 'xeokit-tabs';
  131. const tabClass = 'xeokit-tab';
  132. const tabButtonClass = 'xeokit-tab-btn';
  133. const activeClass = 'active';
  134.  
  135. // Activates the chosen tab and deactivates the rest
  136. function activateTab(chosenTabElement) {
  137. let tabList = chosenTabElement.parentNode.querySelectorAll('.' + tabClass);
  138. for (let i = 0; i < tabList.length; i++) {
  139. let tabElement = tabList[i];
  140. if (tabElement.isEqualNode(chosenTabElement)) {
  141. tabElement.classList.add(activeClass)
  142. } else {
  143. tabElement.classList.remove(activeClass)
  144. }
  145. }
  146. }
  147.  
  148. // Initialize each tabbed container
  149. let tabbedContainers = containerElement.querySelectorAll('.' + tabsClass);
  150. for (let i = 0; i < tabbedContainers.length; i++) {
  151. let tabbedContainer = tabbedContainers[i];
  152. let tabList = tabbedContainer.querySelectorAll('.' + tabClass);
  153. activateTab(tabList[0]);
  154. for (let i = 0; i < tabList.length; i++) {
  155. let tabElement = tabList[i];
  156. let tabButton = tabElement.querySelector('.' + tabButtonClass);
  157. tabButton.addEventListener('click', function (event) {
  158. event.preventDefault();
  159. if (this.classList.contains("disabled")) {
  160. return;
  161. }
  162. activateTab(event.target.parentNode);
  163. })
  164. }
  165. }
  166. }
  167.  
  168.  
  169. /**
  170. * @desc A BIM viewer based on the [xeokit SDK](http://xeokit.io).
  171. *
  172. *
  173. */
  174. class BIMViewer extends Controller {
  175.  
  176. /**
  177. * Constructs a BIMViewer.
  178. * @param {Server} server Data access strategy.
  179. * @param {*} cfg Configuration.
  180. * @param {Boolean} [cfg.enableEditModels=false] Set ````true```` to show "Add", "Edit" and "Delete" options in the Models tab's context menu.
  181. * @param {Boolean} [cfg.keyboardEventsElement] Optional reference to HTML element on which key events should be handled. Defaults to the HTML Document.
  182. */
  183. constructor(server, cfg = {}) {
  184.  
  185. if (!cfg.canvasElement) {
  186. throw "Config expected: canvasElement";
  187. }
  188.  
  189. if (!cfg.explorerElement) {
  190. throw "Config expected: explorerElement";
  191. }
  192.  
  193. if (!cfg.toolbarElement) {
  194. throw "Config expected: toolbarElement";
  195. }
  196.  
  197. if (!cfg.navCubeCanvasElement) {
  198. throw "Config expected: navCubeCanvasElement";
  199. }
  200.  
  201. const canvasElement = cfg.canvasElement;
  202. const explorerElement = cfg.explorerElement;
  203. const inspectorElement = cfg.inspectorElement;
  204. const toolbarElement = cfg.toolbarElement;
  205. const navCubeCanvasElement = cfg.navCubeCanvasElement;
  206. const busyModelBackdropElement = cfg.busyModelBackdropElement;
  207.  
  208. explorerElement.oncontextmenu = (e) => {
  209. e.preventDefault();
  210. };
  211.  
  212. toolbarElement.oncontextmenu = (e) => {
  213. e.preventDefault();
  214. };
  215.  
  216. navCubeCanvasElement.oncontextmenu = (e) => {
  217. e.preventDefault();
  218. };
  219.  
  220. const viewer = new Viewer({
  221. localeService: cfg.localeService,
  222. canvasElement: canvasElement,
  223. keyboardEventsElement: cfg.keyboardEventsElement,
  224. transparent: false,
  225. backgroundColor: [1, 1, 1],
  226. backgroundColorFromAmbientLight: false,
  227. saoEnabled: true,
  228. pbrEnabled: false,
  229. colorTextureEnabled: true
  230. });
  231.  
  232. super(null, cfg, server, viewer);
  233.  
  234. this._configs = {};
  235.  
  236. this._enableAddModels = !!cfg.enableEditModels;
  237. this._enablePropertiesInspector = !!cfg.inspectorElement;
  238.  
  239. /**
  240. * The xeokit [Viewer](https://xeokit.github.io/xeokit-sdk/docs/class/src/viewer/Viewer.js~Viewer.html) at the core of this BIMViewer.
  241. *
  242. * @type {Viewer}
  243. */
  244. this.viewer = viewer;
  245.  
  246. this._objectsKdTree3 = new ObjectsKdTree3(({
  247. viewer
  248. }))
  249.  
  250. this._customizeViewer();
  251. this._initCanvasContextMenus();
  252.  
  253. explorerElement.innerHTML = createExplorerTemplate(cfg);
  254. toolbarElement.innerHTML = createToolbarTemplate();
  255. if (this._enablePropertiesInspector) {
  256. inspectorElement.innerHTML = createInspectorTemplate();
  257. }
  258.  
  259. this._explorerElement = explorerElement;
  260. this._inspectorElement = inspectorElement;
  261.  
  262. initTabs(explorerElement);
  263. if (this._enablePropertiesInspector) {
  264. initTabs(inspectorElement);
  265. }
  266.  
  267. this._modelsExplorer = new ModelsExplorer(this, {
  268. modelsTabElement: explorerElement.querySelector(".xeokit-modelsTab"),
  269. loadModelsButtonElement: explorerElement.querySelector(".xeokit-loadAllModels"), // Can be undefined
  270. unloadModelsButtonElement: explorerElement.querySelector(".xeokit-unloadAllModels"),
  271. addModelButtonElement: explorerElement.querySelector(".xeokit-addModel"), // Can be undefined
  272. modelsElement: explorerElement.querySelector(".xeokit-models"),
  273. enableEditModels: this._enableAddModels
  274. });
  275.  
  276. this._objectsExplorer = new ObjectsExplorer(this, {
  277. objectsTabElement: explorerElement.querySelector(".xeokit-objectsTab"),
  278. showAllObjectsButtonElement: explorerElement.querySelector(".xeokit-showAllObjects"),
  279. hideAllObjectsButtonElement: explorerElement.querySelector(".xeokit-hideAllObjects"),
  280. objectsElement: explorerElement.querySelector(".xeokit-objects")
  281. });
  282.  
  283. this._classesExplorer = new ClassesExplorer(this, {
  284. classesTabElement: explorerElement.querySelector(".xeokit-classesTab"),
  285. showAllClassesButtonElement: explorerElement.querySelector(".xeokit-showAllClasses"),
  286. hideAllClassesButtonElement: explorerElement.querySelector(".xeokit-hideAllClasses"),
  287. classesElement: explorerElement.querySelector(".xeokit-classes")
  288. });
  289.  
  290. this._storeysExplorer = new StoreysExplorer(this, {
  291. storeysTabElement: explorerElement.querySelector(".xeokit-storeysTab"),
  292. showAllStoreysButtonElement: explorerElement.querySelector(".xeokit-showAllStoreys"),
  293. hideAllStoreysButtonElement: explorerElement.querySelector(".xeokit-hideAllStoreys"),
  294. storeysElement: explorerElement.querySelector(".xeokit-storeys")
  295. });
  296.  
  297. if (this._enablePropertiesInspector) {
  298. this._propertiesInspector = new PropertiesInspector(this, {
  299. propertiesTabElement: inspectorElement.querySelector(".xeokit-propertiesTab"),
  300. propertiesElement: inspectorElement.querySelector(".xeokit-properties")
  301. });
  302. }
  303.  
  304. this._resetAction = new ResetAction(this, {
  305. buttonElement: toolbarElement.querySelector(".xeokit-reset"),
  306. active: false
  307. });
  308.  
  309. this._fitAction = new FitAction(this, {
  310. buttonElement: toolbarElement.querySelector(".xeokit-fit"),
  311. active: false
  312. });
  313.  
  314. // Allows Three-D and First Person toggle buttons to cooperatively switch
  315. // CameraControl#navMode between "orbit", "firstPerson" and "planView" modes
  316.  
  317. const cameraControlNavModeMediator = new (function (bimViewer) {
  318.  
  319. let threeDActive = false;
  320. let firstPersonActive = false;
  321.  
  322. this.setThreeDModeActive = (active) => {
  323. if (active) {
  324. bimViewer._firstPersonMode.setActive(false);
  325. bimViewer._marqueeSelectionTool.setEnabled(true);
  326. bimViewer.viewer.cameraControl.navMode = "orbit";
  327. } else {
  328. bimViewer._marqueeSelectionTool.setEnabled(false);
  329. bimViewer._marqueeSelectionTool.setActive(false);
  330. bimViewer._firstPersonMode.setActive(false);
  331. bimViewer.viewer.cameraControl.navMode = "planView";
  332. }
  333. threeDActive = active;
  334. };
  335.  
  336. this.setFirstPersonModeActive = (active) => {
  337. bimViewer.viewer.cameraControl.navMode = active ? "firstPerson" : (threeDActive ? "orbit" : "planView");
  338. firstPersonActive = active;
  339. };
  340.  
  341. })(this);
  342.  
  343. this._threeDMode = new ThreeDMode(this, {
  344. buttonElement: toolbarElement.querySelector(".xeokit-threeD"),
  345. cameraControlNavModeMediator,
  346. active: false
  347. });
  348.  
  349. this._orthoMode = new OrthoMode(this, {
  350. buttonElement: toolbarElement.querySelector(".xeokit-ortho"),
  351. active: false
  352. });
  353.  
  354. this._firstPersonMode = new FirstPersonMode(this, {
  355. buttonElement: toolbarElement.querySelector(".xeokit-firstPerson"),
  356. cameraControlNavModeMediator,
  357. active: false
  358. });
  359.  
  360. this._hideTool = new HideTool(this, {
  361. buttonElement: toolbarElement.querySelector(".xeokit-hide"),
  362. active: false
  363. });
  364.  
  365. this._selectionTool = new SelectionTool(this, {
  366. buttonElement: toolbarElement.querySelector(".xeokit-select"),
  367. active: false
  368. });
  369.  
  370. this._marqueeSelectionTool = new MarqueeSelectionTool(this, {
  371. buttonElement: toolbarElement.querySelector(".xeokit-marquee"),
  372. active: false,
  373. objectsKdTree3: this._objectsKdTree3
  374. });
  375.  
  376. this._showSpacesMode = new ShowSpacesMode(this, {
  377. buttonElement: toolbarElement.querySelector(".xeokit-showSpaces"),
  378. active: false
  379. });
  380.  
  381. this._queryTool = new QueryTool(this, {
  382. active: false
  383. });
  384.  
  385. this._sectionTool = new SectionTool(this, {
  386. buttonElement: toolbarElement.querySelector(".xeokit-section"),
  387. counterElement: toolbarElement.querySelector(".xeokit-section-counter"),
  388. menuButtonElement: toolbarElement.querySelector(".xeokit-section-menu-button"),
  389. menuButtonArrowElement: toolbarElement.querySelector(".xeokit-section-menu-button-arrow"),
  390. active: false
  391. });
  392.  
  393. this._navCubeMode = new NavCubeMode(this, {
  394. navCubeCanvasElement: navCubeCanvasElement,
  395. active: true
  396. });
  397.  
  398. this._busyModal = new BusyModal(this, {
  399. busyModalBackdropElement: busyModelBackdropElement
  400. });
  401.  
  402. this._threeDMode.setActive(true);
  403. this._firstPersonMode.setActive(false);
  404. this._navCubeMode.setActive(true);
  405.  
  406. this._modelsExplorer.on("modelLoaded", (modelId) => {
  407. if (this._modelsExplorer.getNumModelsLoaded() > 0) {
  408. this.setControlsEnabled(true);
  409. }
  410. this.fire("modelLoaded", modelId);
  411. });
  412.  
  413. this._modelsExplorer.on("modelUnloaded", (modelId) => {
  414. if (this._modelsExplorer.getNumModelsLoaded() === 0) {
  415. this.setControlsEnabled(false);
  416. this.openTab("models");
  417. }
  418. this.fire("modelUnloaded", modelId);
  419. });
  420.  
  421. this._resetAction.on("reset", () => {
  422. this.fire("reset", true);
  423. });
  424.  
  425. this._mutexActivation([this._hideTool, this._selectionTool, this._marqueeSelectionTool, this._sectionTool]);
  426.  
  427. explorerElement.querySelector(".xeokit-showAllObjects").addEventListener("click", (event) => {
  428. this.setAllObjectsVisible(true);
  429. this.setAllObjectsXRayed(false);
  430. event.preventDefault();
  431. });
  432.  
  433. explorerElement.querySelector(".xeokit-hideAllObjects").addEventListener("click", (event) => {
  434. this.setAllObjectsVisible(false);
  435. event.preventDefault();
  436. });
  437.  
  438. explorerElement.querySelector(".xeokit-showAllClasses").addEventListener("click", (event) => {
  439. this.setAllObjectsVisible(true);
  440. this.setAllObjectsXRayed(false);
  441. event.preventDefault();
  442. });
  443.  
  444. explorerElement.querySelector(".xeokit-hideAllClasses").addEventListener("click", (event) => {
  445. this.setAllObjectsVisible(false);
  446. event.preventDefault();
  447. });
  448.  
  449. explorerElement.querySelector(".xeokit-showAllStoreys").addEventListener("click", (event) => {
  450. this.setAllObjectsVisible(true);
  451. this.setAllObjectsXRayed(false);
  452. event.preventDefault();
  453. });
  454.  
  455. explorerElement.querySelector(".xeokit-hideAllStoreys").addEventListener("click", (event) => {
  456. this.setAllObjectsVisible(false);
  457. event.preventDefault();
  458. });
  459.  
  460. explorerElement.querySelector(".xeokit-loadAllModels").addEventListener("click", (event) => {
  461. this.setControlsEnabled(false); // For quick UI feedback
  462. this.loadAllModels();
  463. event.preventDefault();
  464. });
  465.  
  466. explorerElement.querySelector(".xeokit-unloadAllModels").addEventListener("click", (event) => {
  467. this.setControlsEnabled(false); // For quick UI feedback
  468. this._modelsExplorer.unloadAllModels();
  469. event.preventDefault();
  470. });
  471.  
  472. if (this._enableAddModels) {
  473. explorerElement.querySelector(".xeokit-addModel").addEventListener("click", (event) => {
  474. this.fire("addModel", {});
  475. event.preventDefault();
  476. });
  477. }
  478.  
  479. this._bcfViewpointsPlugin = new BCFViewpointsPlugin(this.viewer, {});
  480.  
  481. this._fastNavPlugin = new FastNavPlugin(viewer, {
  482. hideEdges: true,
  483. hideSAO: true,
  484. hidePBR: false,
  485. hideColorTexture: false,
  486. hideTransparentObjects: false,
  487. scaleCanvasResolution: false,
  488. scaleCanvasResolutionFactor: 0.6
  489. });
  490.  
  491. this.viewer.scene.on("rendered", () => {
  492. const fastNavPlugin = this._fastNavPlugin;
  493. fastNavPlugin.hideEdges = (hideEdgesMinDrawCount < (stats.frame.drawElements + stats.frame.drawArrays));
  494. fastNavPlugin.scaleCanvasResolution = (scaleCanvasResolutionMinDrawCount < (stats.frame.drawElements + stats.frame.drawArrays));
  495. });
  496.  
  497. this._initConfigs();
  498. this.setControlsEnabled(false);
  499. }
  500.  
  501. /**
  502. * Returns the LocaleService that was configured on this Viewer.
  503. *
  504. * @return {LocaleService} The LocaleService.
  505. */
  506. get localeService() {
  507. return this.viewer.localeService;
  508. }
  509.  
  510. _customizeViewer() {
  511.  
  512. const scene = this.viewer.scene;
  513.  
  514. // Emphasis effects
  515.  
  516. scene.xrayMaterial.fill = false;
  517. scene.xrayMaterial.fillAlpha = 0.3;
  518. scene.xrayMaterial.fillColor = [0, 0, 0];
  519. scene.xrayMaterial.edges = true;
  520. scene.xrayMaterial.edgeAlpha = 0.1;
  521. scene.xrayMaterial.edgeColor = [0, 0, 0];
  522.  
  523. scene.highlightMaterial.edges = true;
  524. scene.highlightMaterial.edgeColor = [1, 1, 0];
  525. scene.highlightMaterial.edgeAlpha = 0.9;
  526. scene.highlightMaterial.fill = true;
  527. scene.highlightMaterial.fillAlpha = 0.1;
  528. scene.highlightMaterial.fillColor = [1, 0, 0];
  529.  
  530. //------------------------------------------------------------------------------------------------------------------
  531. // Configure points material
  532. //------------------------------------------------------------------------------------------------------------------
  533.  
  534. scene.pointsMaterial.pointSize = 1;
  535. scene.pointsMaterial.roundPoints = true;
  536. scene.pointsMaterial.perspectivePoints = true;
  537. scene.pointsMaterial.minPerspectivePointSize = 2;
  538. scene.pointsMaterial.maxPerspectivePointSize = 4;
  539.  
  540. // Camera control
  541.  
  542. this.viewer.cameraControl.panRightClick = true;
  543. this.viewer.cameraControl.followPointer = true;
  544. this.viewer.cameraControl.doublePickFlyTo = false;
  545. this.viewer.cameraControl.smartPivot = true;
  546.  
  547. // Dolly tweaks for best precision when aligning camera for BCF snapshots
  548.  
  549. this.viewer.cameraControl.keyboardDollyRate = 100.0;
  550. this.viewer.cameraControl.mouseWheelDollyRate = 100.0;
  551. this.viewer.cameraControl.dollyInertia = 0;
  552. this.viewer.cameraControl.dollyMinSpeed = 0.04;
  553. this.viewer.cameraControl.dollyProximityThreshold = 30.0;
  554.  
  555. const cameraPivotElement = document.createRange().createContextualFragment("<div class='xeokit-camera-pivot-marker'></div>").firstChild;
  556. document.body.appendChild(cameraPivotElement);
  557. this.viewer.cameraControl.pivotElement = cameraPivotElement;
  558.  
  559. scene.camera.perspective.near = 0.01;
  560. scene.camera.perspective.far = 3000.0;
  561. scene.camera.ortho.near = 0.01;
  562. scene.camera.ortho.far = 2000.0; //
  563.  
  564. // Scalable Ambient Obscurance (SAO) defaults
  565. // Since SAO is non-interactive, set to higher-quality
  566.  
  567. const sao = scene.sao;
  568. sao.enabled = true;
  569. sao.numSamples = 50;
  570. sao.kernelRadius = 200;
  571. }
  572.  
  573. _initCanvasContextMenus() {
  574.  
  575. this._canvasContextMenu = new CanvasContextMenu(this, {
  576. hideOnAction: true
  577. });
  578. this._objectContextMenu = new ObjectContextMenu(this, {
  579. hideOnAction: true
  580. });
  581.  
  582. this.viewer.cameraControl.on("rightClick", (e) => {
  583.  
  584. const event = e.event;
  585.  
  586. const hit = this.viewer.scene.pick({
  587. canvasPos: e.canvasPos
  588. });
  589.  
  590. if (hit && hit.entity.isObject) {
  591. this._canvasContextMenu.hide();
  592. this._objectContextMenu.context = {
  593. viewer: this.viewer,
  594. bimViewer: this,
  595. showObjectInExplorers: (objectId) => {
  596. const openTabId = this.getOpenTab();
  597. if (openTabId !== "objects" && openTabId !== "classes" && openTabId !== "storeys") {
  598. // Scroll won't work if tab not open
  599. this.openTab("objects");
  600. }
  601. this.showObjectInExplorers(objectId);
  602. },
  603. entity: hit.entity
  604. };
  605. this._objectContextMenu.show(e.pagePos[0], e.pagePos[1]);
  606. } else {
  607. this._objectContextMenu.hide();
  608. this._canvasContextMenu.context = {
  609. viewer: this.viewer,
  610. bimViewer: this
  611. };
  612. this._canvasContextMenu.show(e.pagePos[0], e.pagePos[1]);
  613. }
  614. });
  615. }
  616.  
  617. _initConfigs() {
  618. this.setConfigs({
  619. "cameraNear": "0.05",
  620. "cameraFar": "3000.0",
  621. "smartPivot": "true",
  622. "saoEnabled": "true",
  623. "pbrEnabled": "false",
  624. "saoBias": "0.5",
  625. "saoIntensity": "0.15",
  626. "saoNumSamples": "40",
  627. "saoKernelRadius": "100",
  628. "edgesEnabled": true,
  629. "xrayContext": true,
  630. "xrayPickable": false,
  631. "selectedGlowThrough": true,
  632. "highlightGlowThrough": true,
  633. "backgroundColor": [1.0, 1.0, 1.0],
  634. "externalMetadata": false,
  635. "dtxEnabled" : false
  636. });
  637. }
  638.  
  639. /**
  640. * Sets a batch of viewer configurations.
  641. *
  642. * Note that this method is not to be confused with {@link BIMViewer#setViewerState}, which batch-updates various states of the viewer's UI and 3D view.
  643. *
  644. * See [Viewer Configurations](https://xeokit.github.io/xeokit-bim-viewer/docs/#viewer-configurations) for the list of available configurations.
  645. *
  646. * @param {*} viewerConfigs Map of key-value configuration pairs.
  647. */
  648. setConfigs(viewerConfigs) {
  649. for (let name in viewerConfigs) {
  650. if (viewerConfigs.hasOwnProperty(name)) {
  651. const value = viewerConfigs[name];
  652. this.setConfig(name, value);
  653. }
  654. }
  655. }
  656.  
  657. /**
  658. * Sets a viewer configuration.
  659. *
  660. * See [Viewer Configurations](https://xeokit.github.io/xeokit-bim-viewer/docs/#viewer-configurations) for the list of available configurations.
  661. *
  662. * @param {String} name Configuration name.
  663. * @param {*} value Configuration value.
  664. */
  665. setConfig(name, value) {
  666.  
  667. function parseBool(value) {
  668. return ((value === true) || (value === "true"));
  669. }
  670.  
  671. try {
  672. switch (name) {
  673.  
  674. case "backgroundColor":
  675. const rgbColor = value;
  676. this.setBackgroundColor(rgbColor);
  677. this._configs[name] = rgbColor;
  678. break;
  679.  
  680. case "cameraNear":
  681. const near = parseFloat(value);
  682. this.viewer.scene.camera.perspective.near = near;
  683. this.viewer.scene.camera.ortho.near = near;
  684. this._configs[name] = near;
  685. break;
  686.  
  687. case "cameraFar":
  688. const far = parseFloat(value);
  689. this.viewer.scene.camera.perspective.far = far;
  690. // this.viewer.scene.camera.ortho.far = far;
  691. this._configs[name] = far;
  692. break;
  693.  
  694. case "smartPivot":
  695. this.viewer.cameraControl.smartPivot = this._configs[name] = parseBool(value);
  696. break;
  697.  
  698. case "saoEnabled":
  699. this._fastNavPlugin.saoEnabled = this._configs[name] = parseBool(value);
  700. break;
  701.  
  702. case "saoBias":
  703. this.viewer.scene.sao.bias = parseFloat(value);
  704. break;
  705.  
  706. case "saoIntensity":
  707. this.viewer.scene.sao.intensity = parseFloat(value);
  708. break;
  709.  
  710. case "saoKernelRadius":
  711. this.viewer.scene.sao.kernelRadius = this._configs[name] = parseFloat(value);
  712. break;
  713.  
  714. case "saoNumSamples":
  715. this.viewer.scene.sao.numSamples = this._configs[name] = parseFloat(value);
  716. break;
  717.  
  718. case "saoBlur":
  719. this.viewer.scene.sao.blur = this._configs[name] = parseBool(value);
  720. break;
  721.  
  722. case "edgesEnabled":
  723. this._fastNavPlugin.edgesEnabled = this._configs[name] = parseBool(value);
  724. break;
  725.  
  726. case "pbrEnabled":
  727. this._fastNavPlugin.pbrEnabled = this._configs[name] = parseBool(value);
  728. break;
  729.  
  730. case "viewFitFOV":
  731. this.viewer.cameraFlight.fitFOV = this._configs[name] = parseFloat(value);
  732. break;
  733.  
  734. case "viewFitDuration":
  735. this.viewer.cameraFlight.duration = this._configs[name] = parseFloat(value);
  736. break;
  737.  
  738. case "perspectiveFOV":
  739. this.viewer.camera.perspective.fov = this._configs[name] = parseFloat(value);
  740. break;
  741.  
  742. case "excludeUnclassifiedObjects":
  743. this._configs[name] = parseBool(value);
  744. break;
  745.  
  746. case "xrayContext":
  747. this._configs[name] = value;
  748. break;
  749.  
  750. case "xrayPickable":
  751. this._configs[name] = parseBool(value);
  752. break;
  753.  
  754. case "selectedGlowThrough":
  755. const selectedGlowThrough = this._configs[name] = parseBool(value);
  756. const selectedMaterial = this.viewer.scene.selectedMaterial;
  757. selectedMaterial.glowThrough = selectedGlowThrough;
  758. selectedMaterial.fillAlpha = selectedGlowThrough ? 0.5 : 1.0;
  759. selectedMaterial.edgeAlpha = selectedGlowThrough ? 0.5 : 1.0;
  760. break;
  761.  
  762. case "highlightGlowThrough":
  763. const highlightGlowThrough = this._configs[name] = parseBool(value);
  764. const highlightMaterial = this.viewer.scene.highlightMaterial;
  765. highlightMaterial.glowThrough = highlightGlowThrough;
  766. highlightMaterial.fillAlpha = highlightGlowThrough ? 0.5 : 1.0;
  767. highlightMaterial.edgeAlpha = highlightGlowThrough ? 0.5 : 1.0;
  768. break;
  769.  
  770. case "externalMetadata":
  771. this._configs[name] = parseBool(value);
  772. break;
  773.  
  774. case "showSpaces":
  775. this._configs[name] = parseBool(value);
  776. this._showSpacesMode.setActive(value);
  777. break;
  778.  
  779. case "dtxEnabled":
  780. this._configs[name] = parseBool(value);
  781. this.viewer.scene.dtxEnabled = value;
  782. break;
  783.  
  784. case "objectColors":
  785. this._configs[name] = value;
  786. this._modelsExplorer.setObjectColors(value);
  787. break;
  788.  
  789. default:
  790. this.warn("setConfig() - unsupported configuration: '" + name + "'");
  791. }
  792.  
  793. } catch (e) {
  794. this.error("setConfig() - failed to configure '" + name + "': " + e);
  795. }
  796. }
  797.  
  798. /**
  799. * Gets the value of a viewer configuration.
  800. *
  801. * These are set with {@link BIMViewer#setConfig} and {@link BIMViewer#setConfigs}.
  802. *
  803. * @param {String} name Configuration name.
  804. * @ereturns {*} Configuration value.
  805. */
  806. getConfig(name) {
  807. return this._configs[name];
  808. }
  809.  
  810. //------------------------------------------------------------------------------------------------------------------
  811. // Content querying methods
  812. //------------------------------------------------------------------------------------------------------------------
  813.  
  814. /**
  815. * Gets information on all available projects.
  816. * Gets information on all available projects.
  817. *
  818. * See [Getting Info on Available Projects](https://xeokit.github.io/xeokit-bim-viewer/docs/#getting-info-on-available-projects) for usage.
  819. *
  820. * @param {Function} done Callback invoked on success, into which the projects information JSON is passed.
  821. * @param {Function} error Callback invoked on failure, into which the error message string is passed.
  822. */
  823. getProjectsInfo(done, error) {
  824. if (!done) {
  825. this.error("getProjectsInfo() - Argument expected: 'done'");
  826. return;
  827. }
  828. this.server.getProjects(done, (errorMsg) => {
  829. this.error("getProjectsInfo() - " + errorMsg);
  830. if (error) {
  831. error(errorMsg);
  832. }
  833. });
  834. }
  835.  
  836. /**
  837. * Gets information on the given project.
  838. *
  839. * See [Getting Info on a Project](https://xeokit.github.io/xeokit-bim-viewer/docs/#getting-info-on-a-project) for usage.
  840. *
  841. * @param {String} projectId ID of the project to get information on. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
  842. * @param {Function} done Callback invoked on success, into which the project information JSON is passed.
  843. * @param {Function} error Callback invoked on failure, into which the error message string is passed.
  844. */
  845. getProjectInfo(projectId, done, error) {
  846. if (!projectId) {
  847. this.error("getProjectInfo() - Argument expected: projectId");
  848. return;
  849. }
  850. if (!done) {
  851. this.error("getProjectInfo() - Argument expected: 'done'");
  852. return;
  853. }
  854. this.server.getProject(projectId,
  855. done, (errorMsg) => {
  856. this.error("getProjectInfo() - " + errorMsg);
  857. if (error) {
  858. error(errorMsg);
  859. }
  860. });
  861. }
  862.  
  863. /**
  864. * Gets information on the given object, belonging to the given model, within the given project.
  865. *
  866. * See [Getting Info on an Object](https://xeokit.github.io/xeokit-bim-viewer/docs/#getting-info-on-an-object) for usage.
  867. *
  868. * @param {String} projectId ID of the project to get information on. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
  869. * @param {String} modelId ID of a model within the project. Must be the ID of one of the models in the information obtained by {@link BIMViewer#getProjectInfo}.
  870. * @param {String} objectId ID of an object in the model.
  871. * @param {Function} done Callback invoked on success, into which the object information JSON is passed.
  872. * @param {Function} error Callback invoked on failure, into which the error message string is passed.
  873. */
  874. getObjectInfo(projectId, modelId, objectId, done, error) {
  875. if (!projectId) {
  876. this.error("getObjectInfo() - Argument expected: projectId");
  877. return;
  878. }
  879. if (!modelId) {
  880. this.error("getObjectInfo() - Argument expected: modelId");
  881. return;
  882. }
  883. if (!objectId) {
  884. this.error("getObjectInfo() - Argument expected: objectId");
  885. return;
  886. }
  887. if (!done) {
  888. this.error("getProjectInfo() - Argument expected: 'done'");
  889. return;
  890. }
  891. this.server.getObjectInfo(projectId, modelId, objectId,
  892. done,
  893. (errorMsg) => {
  894. if (error) {
  895. error(errorMsg);
  896. }
  897. });
  898. }
  899.  
  900. //------------------------------------------------------------------------------------------------------------------
  901. // Content loading methods
  902. //------------------------------------------------------------------------------------------------------------------
  903.  
  904. /**
  905. * Loads a project into the viewer.
  906. *
  907. * Unloads any currently loaded project and its models first. If the given project is already loaded, will unload that project first.
  908. *
  909. * @param {String} projectId ID of the project to load. Must be the ID of one of the projects in the information obtained by {@link BIMViewer#getProjects}.
  910. * @param {Function} done Callback invoked on success.
  911. * @param {Function} error Callback invoked on failure, into which the error message string is passed.
  912. */
  913. loadProject(projectId, done, error) {
  914. if (!projectId) {
  915. this.error("loadProject() - Argument expected: objectId");
  916. return;
  917. }
  918. this._modelsExplorer.loadProject(projectId,
  919. () => {
  920. if (done) {
  921. done();
  922. }
  923. }, (errorMsg) => {
  924. this.error("loadProject() - " + errorMsg);
  925. if (error) {
  926. error(errorMsg);
  927. }
  928. });
  929. }
  930.  
  931. /**
  932. * Unloads whatever project is currently loaded.
  933. */
  934. unloadProject() {
  935. this._modelsExplorer.unloadProject();
  936. this.openTab("models");
  937. this.setControlsEnabled(false); // For quick UI feedback
  938. }
  939.  
  940. /**
  941. * Returns the ID of the currently loaded project, if any.
  942. *
  943. * @returns {String} The ID of the currently loaded project, otherwise ````null```` if no project is currently loaded.
  944. */
  945. getLoadedProjectId() {
  946. return this._modelsExplorer.getLoadedProjectId();
  947. }
  948.  
  949. /**
  950. * Returns the IDs of the models in the currently loaded project.
  951. *
  952. * @returns {String[]} The IDs of the models in the currently loaded project.
  953. */
  954. getModelIds() {
  955. return this._modelsExplorer.getModelIds();
  956. }
  957.  
  958. /**
  959. * Loads a model into the viewer.
  960. *
  961. * Assumes that the project containing the model is currently loaded.
  962. *
  963. * @param {String} modelId ID of the model to load. Must be the ID of one of the models in the currently loaded project.
  964. * @param {Function} done Callback invoked on success.
  965. * @param {Function} error Callback invoked on failure, into which the error message string is passed.
  966. */
  967. loadModel(modelId, done, error) {
  968. if (!modelId) {
  969. this.error("loadModel() - Argument expected: modelId");
  970. return;
  971. }
  972. this._modelsExplorer.loadModel(modelId,
  973. () => {
  974. if (done) {
  975. done();
  976. }
  977. }, (errorMsg) => {
  978. this.error("loadModel() - " + errorMsg);
  979. if (error) {
  980. error(errorMsg);
  981. }
  982. });
  983. }
  984.  
  985. /**
  986. * Load all models in the currently loaded project.
  987. *
  988. * Doesn't reload any models that are currently loaded.
  989. *
  990. * @param {Function} done Callback invoked on successful loading of the models.
  991. */
  992. loadAllModels(done = function () {
  993. }) {
  994. const modelIds = this._modelsExplorer.getModelIds();
  995. const loadNextModel = (i, done2) => {
  996. if (i >= modelIds.length) {
  997. done2();
  998. } else {
  999. const modelId = modelIds[i];
  1000. if (!this._modelsExplorer.isModelLoaded(modelId)) {
  1001. this._modelsExplorer.loadModel(modelId, () => {
  1002. loadNextModel(i + 1, done2);
  1003. }, (errorMsg) => {
  1004. this.error("loadAllModels() - " + errorMsg);
  1005. loadNextModel(i + 1, done2);
  1006. });
  1007. } else {
  1008. loadNextModel(i + 1, done2);
  1009. }
  1010. }
  1011. };
  1012. loadNextModel(0, done);
  1013. }
  1014.  
  1015. /**
  1016. * Returns the IDs of the currently loaded models, if any.
  1017. *
  1018. * @returns {String[]} The IDs of the currently loaded models, otherwise an empty array if no models are currently loaded.
  1019. */
  1020. getLoadedModelIds() {
  1021. return this._modelsExplorer._getLoadedModelIds();
  1022. }
  1023.  
  1024. /**
  1025. * Gets if the given model is loaded.
  1026. *
  1027. * @param {String} modelId ID of the model to check. Must be the ID of one of the models in the currently loaded project.
  1028. * @returns {Boolean} True if the given model is loaded.
  1029. */
  1030. isModelLoaded(modelId) {
  1031. if (!modelId) {
  1032. this.error("unloadModel() - Argument expected: modelId");
  1033. return;
  1034. }
  1035. return this._modelsExplorer.isModelLoaded(modelId);
  1036. }
  1037.  
  1038. /**
  1039. * Unloads a model from the viewer.
  1040. *
  1041. * Does nothing if the model is not currently loaded.
  1042. *
  1043. * @param {String} modelId ID of the model to unload.
  1044. */
  1045. unloadModel(modelId) {
  1046. if (!modelId) {
  1047. this.error("unloadModel() - Argument expected: modelId");
  1048. return;
  1049. }
  1050. this._modelsExplorer.unloadModel(modelId);
  1051. }
  1052.  
  1053. /**
  1054. * Unloads all currently loaded models.
  1055. */
  1056. unloadAllModels() {
  1057. this._modelsExplorer.unloadAllModels();
  1058. }
  1059.  
  1060. /**
  1061. * Edits a model.
  1062. *
  1063. * Assumes that the project containing the model is currently loaded.
  1064. *
  1065. * @param {String} modelId ID of the model to edit. Must be the ID of one of the models in the currently loaded project.
  1066. */
  1067. editModel(modelId) {
  1068. this.fire("editModel", {
  1069. modelId: modelId
  1070. });
  1071. }
  1072.  
  1073. /**
  1074. * Deletes a model.
  1075. *
  1076. * Assumes that the project containing the model is currently loaded.
  1077. *
  1078. * @param {String} modelId ID of the model to delete. Must be the ID of one of the models in the currently loaded project.
  1079. */
  1080. deleteModel(modelId) {
  1081. this.fire("deleteModel", {
  1082. modelId: modelId
  1083. });
  1084. }
  1085.  
  1086. /**
  1087. * Adds a model.
  1088. *
  1089. */
  1090. addModel() {
  1091. this.fire("addModel", {});
  1092. }
  1093.  
  1094. /**
  1095. * Sets the viewer's background color.
  1096. *
  1097. * @param {Number[]} rgbColor Three-element array of RGB values, each in range ````[0..1]````.
  1098. */
  1099. setBackgroundColor(rgbColor) {
  1100. this.viewer.scene.canvas.backgroundColor = rgbColor;
  1101. }
  1102.  
  1103. /**
  1104. * Sets where the colors for model objects will be loaded from.
  1105. *
  1106. * Options are:
  1107. *
  1108. * * "model" - (default) load colors from models, and
  1109. * * "viewer" - load colors from the viewer's inbuilt table of colors for IFC types.
  1110. *
  1111. * This is "model" by default.
  1112. *
  1113. * @deprecated
  1114. * @param {String} source Where colors will be loaded from - "model" or "viewer".
  1115. */
  1116. setObjectColorSource(source) {
  1117. console.log("BIMViewer.setObjectColorSource() is now deprecated and no longer functional. By default, " +
  1118. "BIMViewer.getObjectColorSource() will now always return the (formerly) default value of `model`.");
  1119. }
  1120.  
  1121. /**
  1122. * Gets where the colors for model objects will be loaded from.
  1123. *
  1124. * This is "model" by default.
  1125. *
  1126. * @deprecated
  1127. * @return {String} Where colors will be loaded from - "model" to get colors from the model, or "viewer" to get them from the viewer's built-in table of colors for IFC types.
  1128. */
  1129. getObjectColorSource() {
  1130. return "model";
  1131. }
  1132.  
  1133. /**
  1134. * Updates viewer UI state according to the properties in the given object.
  1135. *
  1136. * Note that, since some updates could be animated (e.g. flying the camera to fit objects to view) this
  1137. * method optionally takes a callback, which it invokes after updating the UI.
  1138. *
  1139. * Also, this method is not to be confused with {@link BIMViewer#setConfigs}, which is used to batch-update various configurations and user preferences on the viewer.
  1140. *
  1141. * See [Viewer States](https://xeokit.github.io/xeokit-bim-viewer/docs/#viewer_states) for the list of states that may be batch-updated with this method.
  1142. *
  1143. * @param {Object} viewerState Specifies the viewer UI state updates.
  1144. * @param {Function} done Callback invoked on successful update of the viewer states.
  1145. */
  1146. setViewerState(viewerState, done = () => {
  1147. }) {
  1148. if (viewerState.tabOpen) {
  1149. this.openTab(viewerState.tabOpen);
  1150. }
  1151. if (viewerState.expandObjectsTree) {
  1152. this._objectsExplorer.expandTreeViewToDepth(viewerState.expandObjectsTree);
  1153. }
  1154. if (viewerState.expandClassesTree) {
  1155. this._classesExplorer.expandTreeViewToDepth(viewerState.expandClassesTree);
  1156. }
  1157. if (viewerState.expandStoreysTree) {
  1158. this._storeysExplorer.expandTreeViewToDepth(viewerState.expandStoreysTree);
  1159. }
  1160. if (viewerState.setCamera) {
  1161. this.setCamera(viewerState.setCamera);
  1162. }
  1163. this._parseSelectedStorey(viewerState, () => {
  1164. this._parseThreeDMode(viewerState, () => {
  1165. done();
  1166. });
  1167. });
  1168. }
  1169.  
  1170. _parseSelectedStorey(viewerState, done) {
  1171. if (viewerState.selectedStorey) {
  1172. this.selectStorey(viewerState.selectedStorey);
  1173. done();
  1174. } else {
  1175. done();
  1176. }
  1177. }
  1178.  
  1179. _parseThreeDMode(viewerState, done) {
  1180. const activateThreeDMode = (viewerState.threeDActive !== false);
  1181. this.set3DEnabled(activateThreeDMode, done);
  1182. }
  1183.  
  1184. /**
  1185. * Highlights the given object in the tree views within the Objects, Classes and Storeys tabs.
  1186. *
  1187. * Also scrolls the object's node into view within each tree, then highlights it.
  1188. *
  1189. * De-highlights whatever node is currently highlighted in each of those trees.
  1190. *
  1191. * @param {String} objectId ID of the object
  1192. */
  1193. showObjectInExplorers(objectId) {
  1194. if (!objectId) {
  1195. this.error("showObjectInExplorers() - Argument expected: objectId");
  1196. return;
  1197. }
  1198. this._objectsExplorer.showNodeInTreeView(objectId);
  1199. this._classesExplorer.showNodeInTreeView(objectId);
  1200. this._storeysExplorer.showNodeInTreeView(objectId);
  1201. this.fire("openExplorer", {});
  1202. }
  1203.  
  1204. /**
  1205. * De-highlights the object previously highlighted with {@link BIMViewer#showObjectInExplorers}.
  1206. *
  1207. * This only de-highlights the node. If the node is currently scrolled into view, then the node will remain in view.
  1208. *
  1209. * For each tab, does nothing if a node is currently highlighted.
  1210. */
  1211. unShowObjectInExplorers() {
  1212. this._objectsExplorer.unShowNodeInTreeView();
  1213. this._classesExplorer.unShowNodeInTreeView();
  1214. this._storeysExplorer.unShowNodeInTreeView();
  1215. }
  1216.  
  1217. /**
  1218. * Shows the properties of the given object in the Properties tab.
  1219. *
  1220. * @param {String} objectId ID of the object
  1221. */
  1222. showObjectProperties(objectId) {
  1223. if (!objectId) {
  1224. this.error("showObjectInExplorers() - Argument expected: objectId");
  1225. return;
  1226. }
  1227. if (this._enablePropertiesInspector) {
  1228. this._propertiesInspector.showObjectPropertySets(objectId);
  1229. }
  1230. this.fire("openInspector", {});
  1231. }
  1232.  
  1233. /**
  1234. * Sets whether or not the given objects are visible.
  1235. *
  1236. * @param {String[]} objectIds IDs of objects.
  1237. * @param {Boolean} visible True to set objects visible, false to set them invisible.
  1238. */
  1239. setObjectsVisible(objectIds, visible) {
  1240. this._withObjectsInSubtree(objectIds, (entity) => {
  1241. entity.visible = visible;
  1242. });
  1243. }
  1244.  
  1245. /**
  1246. * Sets the visibility of all objects.
  1247. *
  1248. * @param {Boolean} visible True to set objects visible, false to set them invisible.
  1249. */
  1250. setAllObjectsVisible(visible) {
  1251. if (visible) {
  1252. this.viewer.scene.setObjectsVisible(this.viewer.scene.objectIds, true);
  1253. } else {
  1254. this.viewer.scene.setObjectsVisible(this.viewer.scene.visibleObjectIds, false);
  1255. }
  1256. }
  1257.  
  1258. /**
  1259. * Sets whether or not the given objects are X-rayed.
  1260. *
  1261. * @param {String[]} objectIds IDs of objects.
  1262. * @param {Boolean} xrayed Whether or not to X-ray the objects.
  1263. */
  1264. setObjectsXRayed(objectIds, xrayed) {
  1265. this._withObjectsInSubtree(objectIds, (entity) => {
  1266. entity.xrayed = xrayed;
  1267. });
  1268. }
  1269.  
  1270. /**
  1271. * Sets whether or not all objects are X-rayed.
  1272. *
  1273. * @param {Boolean} xrayed Whether or not to set all objects X-rayed.
  1274. */
  1275. setAllObjectsXRayed(xrayed) {
  1276. if (xrayed) {
  1277. this.viewer.scene.setObjectsXRayed(this.viewer.scene.objectIds, true);
  1278. } else {
  1279. this.viewer.scene.setObjectsXRayed(this.viewer.scene.xrayedObjectIds, false);
  1280. }
  1281. }
  1282.  
  1283. /**
  1284. * Sets whether or not the given objects are selected.
  1285. *
  1286. * @param {String[]} objectIds IDs of objects.
  1287. * @param {Boolean} selected Whether or not to set the objects selected.
  1288. */
  1289. setObjectsSelected(objectIds, selected) {
  1290. this._withObjectsInSubtree(objectIds, (entity) => {
  1291. entity.selected = selected;
  1292. });
  1293. }
  1294.  
  1295. /**
  1296. * Sets whether or not all objects are selected.
  1297. *
  1298. * @param {Boolean} selected Whether or not to set all objects selected.
  1299. */
  1300. setAllObjectsSelected(selected) {
  1301. if (selected) {
  1302. this.viewer.scene.setObjectsSelected(this.viewer.scene.objectIds, true);
  1303. } else {
  1304. this.viewer.scene.setObjectsSelected(this.viewer.scene.selectedObjectIds, false);
  1305. }
  1306. }
  1307.  
  1308. _withObjectsInSubtree(objectIds, callback) {
  1309. if (!objectIds) {
  1310. this.error("Argument expected: objectIds");
  1311. return;
  1312. }
  1313. for (let i = 0, len = objectIds.length; i < len; i++) {
  1314. const objectId = objectIds[i];
  1315. this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
  1316. const entity = this.viewer.scene.objects[metaObject.id];
  1317. if (entity) {
  1318. callback(entity);
  1319. }
  1320. });
  1321. }
  1322. }
  1323.  
  1324. /**
  1325. * Flies the camera to fit the given object in view.
  1326. *
  1327. * @param {String} objectId ID of the object
  1328. * @param {Function} done Callback invoked on completion
  1329. */
  1330. flyToObject(objectId, done) {
  1331. if (!objectId) {
  1332. this.error("flyToObject() - Argument expected: objectId");
  1333. return;
  1334. }
  1335. const viewer = this.viewer;
  1336. const scene = viewer.scene;
  1337. const objectIds = [];
  1338. this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
  1339. if (scene.objects[metaObject.id]) {
  1340. objectIds.push(metaObject.id);
  1341. }
  1342. });
  1343. if (objectIds.length === 0) {
  1344. this.error("Object not found in viewer: '" + objectId + "'");
  1345. if (done) {
  1346. done();
  1347. }
  1348. return;
  1349. }
  1350. scene.setObjectsVisible(objectIds, true);
  1351. scene.setObjectsHighlighted(objectIds, true);
  1352. const aabb = scene.getAABB(objectIds);
  1353. viewer.cameraFlight.flyTo({
  1354. aabb: aabb
  1355. }, () => {
  1356. if (done) {
  1357. done();
  1358. }
  1359. setTimeout(function () {
  1360. scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
  1361. }, 500);
  1362. });
  1363. viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
  1364. }
  1365.  
  1366. /**
  1367. * Flies the camera to fit the given objects in view.
  1368. *
  1369. * @param {String[]} objectIds IDs of the objects
  1370. * @param {Function} done Callback invoked on completion
  1371. */
  1372. viewFitObjects(objectIds, done) {
  1373. if (!objectIds) {
  1374. this.error("flyToObject() - Argument expected: objectIds");
  1375. return;
  1376. }
  1377. const viewer = this.viewer;
  1378. const scene = viewer.scene;
  1379.  
  1380. const entityIds = [];
  1381.  
  1382. for (var i = 0, len = objectIds.length; i < len; i++) {
  1383. const objectId = objectIds[i];
  1384. this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
  1385. if (scene.objects[metaObject.id]) {
  1386. entityIds.push(metaObject.id);
  1387. }
  1388. });
  1389. }
  1390. if (entityIds.length === 0) {
  1391. if (done) {
  1392. done();
  1393. }
  1394. return;
  1395. }
  1396. scene.setObjectsVisible(entityIds, true);
  1397. scene.setObjectsHighlighted(entityIds, true);
  1398. const aabb = scene.getAABB(entityIds);
  1399. viewer.cameraFlight.flyTo({
  1400. aabb: aabb
  1401. }, () => {
  1402. if (done) {
  1403. done();
  1404. }
  1405. setTimeout(function () {
  1406. scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
  1407. }, 500);
  1408. });
  1409. viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
  1410. }
  1411.  
  1412. /**
  1413. * Flies the camera to fit all objects in view.
  1414. *
  1415. * @param {Function} done Callback invoked on completion
  1416. */
  1417. viewFitAll(done) {
  1418. const viewer = this.viewer;
  1419. const scene = viewer.scene;
  1420. const aabb = scene.getAABB();
  1421. viewer.cameraFlight.flyTo({
  1422. aabb: aabb
  1423. }, () => {
  1424. if (done) {
  1425. done();
  1426. }
  1427. });
  1428. viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
  1429. }
  1430.  
  1431. /**
  1432. * Jumps the camera to fit the given object in view.
  1433. *
  1434. * @param {String} objectId ID of the object
  1435. */
  1436. jumpToObject(objectId) {
  1437. if (!objectId) {
  1438. this.error("jumpToObject() - Argument expected: objectId");
  1439. return;
  1440. }
  1441. const viewer = this.viewer;
  1442. const scene = viewer.scene;
  1443. const objectIds = [];
  1444. this.viewer.metaScene.withMetaObjectsInSubtree(objectId, (metaObject) => {
  1445. if (scene.objects[metaObject.id]) {
  1446. objectIds.push(metaObject.id);
  1447. }
  1448. });
  1449. if (objectIds.length === 0) {
  1450. this.error("Object not found in viewer: '" + objectId + "'");
  1451. return;
  1452. }
  1453. scene.setObjectsVisible(objectIds, true);
  1454. const aabb = scene.getAABB(objectIds);
  1455. viewer.cameraFlight.jumpTo({
  1456. aabb: aabb
  1457. });
  1458. viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
  1459. }
  1460.  
  1461. /**
  1462. * Sets the camera to the given position.
  1463. *
  1464. * @param {Number[]} [params.eye] Eye position.
  1465. * @param {Number[]} [params.look] Point of interest.
  1466. * @param {Number[]} [params.up] Direction of "up".
  1467. */
  1468. setCamera(params) {
  1469. const viewer = this.viewer;
  1470. const scene = viewer.scene;
  1471. const camera = scene.camera;
  1472. if (params.eye) {
  1473. camera.eye = params.eye;
  1474. }
  1475. if (params.look) {
  1476. camera.look = params.look;
  1477. }
  1478. if (params.up) {
  1479. camera.up = params.up;
  1480. }
  1481. }
  1482.  
  1483. /**
  1484. * Fits the given models in view.
  1485. *
  1486. * @param {String[]} modelIds ID of the models.
  1487. * @param {Function} [done] Callback invoked on completion. Will be animated if this is given, otherwise will be instantaneous.
  1488. */
  1489. viewFitModels(modelIds, done) {
  1490. if (!modelIds) {
  1491. this.error("viewFitModels() - Argument expected: modelIds");
  1492. return;
  1493. }
  1494. const viewer = this.viewer;
  1495. const scene = viewer.scene;
  1496. const aabb = math.AABB3();
  1497. math.collapseAABB3(aabb);
  1498. for (var i = 0, len = modelIds.length; i < len; i++) {
  1499. const modelId = modelIds[i];
  1500. const model = scene.models[modelId];
  1501. if (!model) {
  1502. this.error("Model not found in viewer: '" + modelId + "'");
  1503. continue;
  1504. }
  1505. model.visible = true;
  1506. model.highlighted = true;
  1507. math.expandAABB3(aabb, model.aabb);
  1508. }
  1509. if (done) {
  1510. viewer.cameraFlight.flyTo({
  1511. aabb: aabb
  1512. }, () => {
  1513. done();
  1514. setTimeout(function () {
  1515. scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
  1516. }, 500);
  1517. });
  1518. } else {
  1519. viewer.cameraFlight.jumpTo({
  1520. aabb: aabb
  1521. });
  1522. setTimeout(function () {
  1523. scene.setObjectsHighlighted(scene.highlightedObjectIds, false);
  1524. }, 500);
  1525. }
  1526. viewer.cameraControl.pivotPos = math.getAABB3Center(aabb);
  1527. }
  1528.  
  1529. /**
  1530. * Opens the specified viewer tab.
  1531. *
  1532. * The available tabs are:
  1533. *
  1534. * * "models" - the Models tab, which lists the models available within the currently loaded project,
  1535. * * "objects" - the Objects tab, which contains a tree view for each loaded model, organized to indicate the containment hierarchy of their objects,
  1536. * * "classes" - the Classes tab, which contains a tree view for each loaded model, with nodes grouped by IFC types of their objects,
  1537. * * "storeys" - the Storeys tab, which contains a tree view for each loaded model, with nodes grouped within ````IfcBuildingStoreys````, sub-grouped by their IFC types, and
  1538. * * "properties" - the Properties tab, which shows property sets for a given object.
  1539. *
  1540. * @param {String} tabId ID of the tab to open - see method description.
  1541. */
  1542. openTab(tabId) {
  1543. if (!tabId) {
  1544. this.error("openTab() - Argument expected: tabId");
  1545. return;
  1546. }
  1547. let tabSelector;
  1548. switch (tabId) {
  1549. case "models":
  1550. tabSelector = "xeokit-modelsTab";
  1551. break;
  1552. case "objects":
  1553. tabSelector = "xeokit-objectsTab";
  1554. break;
  1555. case "classes":
  1556. tabSelector = "xeokit-classesTab";
  1557. break;
  1558. case "storeys":
  1559. tabSelector = "xeokit-storeysTab";
  1560. break;
  1561. case "properties":
  1562. tabSelector = "xeokit-propertiesTab";
  1563. break;
  1564. default:
  1565. this.error("openTab() - tab not recognized: '" + tabId + "'");
  1566. return;
  1567. }
  1568. this._openTab(this._explorerElement, tabSelector);
  1569. // this._openTab(this._inspectorElement, tabSelector);
  1570. }
  1571.  
  1572. _openTab(element, tabSelector) {
  1573. const tabClass = 'xeokit-tab';
  1574. const activeClass = 'active';
  1575. let tabs = element.querySelectorAll("." + tabClass);
  1576. let tab = element.querySelector("." + tabSelector);
  1577. for (let i = 0; i < tabs.length; i++) {
  1578. let tabElement = tabs[i];
  1579. if (tabElement.isEqualNode(tab)) {
  1580. tabElement.classList.add(activeClass)
  1581. } else {
  1582. tabElement.classList.remove(activeClass)
  1583. }
  1584. }
  1585. }
  1586.  
  1587. /**
  1588. * Returns the ID of the currently open viewer tab.
  1589. *
  1590. * The available tabs are:
  1591. *
  1592. * * "models" - the Models tab, which lists the models available within the currently loaded project,
  1593. * * "objects" - the Objects tab, which contains a tree view for each loaded model, organized to indicate the containment hierarchy of their objects,
  1594. * * "classes" - the Classes tab, which contains a tree view for each loaded model, with nodes grouped by IFC types of their objects,
  1595. * * "storeys" - the Storeys tab, which contains a tree view for each loaded model, with nodes grouped within ````IfcBuildingStoreys````, sub-grouped by their IFC types,
  1596. * * "properties" - the Properties tab, which shows property sets for a given object, and
  1597. * * "none" - no tab is open; this is unlikely, since one of the above tabs should be open at a any time, but here for robustness.
  1598. */
  1599. getOpenTab() {
  1600. function hasClass(element, className) {
  1601. if (!element) {
  1602. return false;
  1603. }
  1604. return (" " + element.className + " ").indexOf(" " + className + " ") > -1;
  1605. }
  1606.  
  1607. const activeClass = 'active';
  1608. let modelsTab = this._explorerElement.querySelector(".xeokit-modelsTab");
  1609. if (hasClass(modelsTab, activeClass)) {
  1610. return "models";
  1611. }
  1612. let objectsTab = this._explorerElement.querySelector(".xeokit-objectsTab");
  1613. if (hasClass(objectsTab, activeClass)) {
  1614. return "objects";
  1615. }
  1616. let classesTab = this._explorerElement.querySelector(".xeokit-classesTab");
  1617. if (hasClass(classesTab, activeClass)) {
  1618. return "classes";
  1619. }
  1620. let storeysTab = this._explorerElement.querySelector(".xeokit-storeysTab");
  1621. if (hasClass(storeysTab, activeClass)) {
  1622. return "storeys";
  1623. }
  1624. let propertiesTab = this._inspectorElement.querySelector(".xeokit-propertiesTab");
  1625. if (hasClass(propertiesTab, activeClass)) {
  1626. return "properties";
  1627. }
  1628. return "none";
  1629. }
  1630.  
  1631. /**
  1632. * Switches the viewer between 2D and 3D viewing modes.
  1633. *
  1634. * @param {Boolean} enabled Set true to switch into 3D mode, else false to switch into 2D mode.
  1635. * @param {Function} done Callback to invoke when switch complete. Supplying this callback causes an animated transition. Otherwise, the transition will be instant.
  1636. */
  1637. set3DEnabled(enabled, done) {
  1638. this._threeDMode.setActive(enabled, done);
  1639. }
  1640.  
  1641. /**
  1642. * Gets whether the viewer is in 3D or 2D viewing mode.
  1643. *
  1644. * @returns {boolean} True when in 3D mode, else false.
  1645. */
  1646. get3DEnabled() {
  1647. return this._threeDMode.getActive();
  1648. }
  1649.  
  1650. /**
  1651. * Sets whether IFCSpace types are ever shown.
  1652. *
  1653. * @param {Boolean} shown Set true to allow IFCSpaces to be shown, else false to always keep them hidden.
  1654. */
  1655. setSpacesShown(shown) {
  1656. this._showSpacesMode.setActive(shown);
  1657. }
  1658.  
  1659. /**
  1660. * Gets whether the viewer allows IFCSpace types to be shown.
  1661. *
  1662. * @returns {boolean} True to allow IFCSpaces to be shown, else false to always keep them hidden.
  1663. */
  1664. getSpacesShown() {
  1665. return this._showSpacesMode.getActive();
  1666. }
  1667.  
  1668. /**
  1669. * Sets whether the viewer is in orthographic viewing mode.
  1670. *
  1671. * The viewer is either in orthographic mode or perspective mode. The viewer is in perspective mode by default.
  1672. *
  1673. * @param {Boolean} enabled Set true to switch into ortho mode, else false to switch into perspective mode.
  1674. * @param {Function} done Callback to invoke when switch complete. Supplying this callback causes an animated transition. Otherwise, the transition will be instant.
  1675. */
  1676. setOrthoEnabled(enabled, done) {
  1677. this._orthoMode.setActive(enabled, done);
  1678. }
  1679.  
  1680. /**
  1681. * Gets whether the viewer is in orthographic viewing mode.
  1682. *
  1683. * The viewer is either in orthographic mode or perspective mode. The viewer is in perspective mode by default.
  1684. *
  1685. * @returns {boolean} True when in ortho mode, else false when in perspective mode.
  1686. */
  1687. getOrthoEnabled() {
  1688. return this._orthoMode.getActive();
  1689. }
  1690.  
  1691. /**
  1692. * Transitions the viewer into an isolated view of the given building storey.
  1693. *
  1694. * Does nothing and logs an error if no object of the given ID is in the viewer, or if the object is not an ````IfcBuildingStorey````.
  1695. *
  1696. * @param {String} storeyObjectId ID of an ````IfcBuildingStorey```` object.
  1697. * @param {Function} [done] Optional callback to invoke on completion. When provided, the transition will be animated, with the camera flying into position. Otherwise, the transition will be instant, with the camera jumping into position.
  1698. */
  1699. selectStorey(storeyObjectId, done) {
  1700. const metaScene = this.viewer.metaScene;
  1701. const storeyMetaObject = metaScene.metaObjects[storeyObjectId];
  1702. if (!storeyMetaObject) {
  1703. this.error("selectStorey() - Object is not found: '" + storeyObjectId + "'");
  1704. return;
  1705. }
  1706. if (storeyMetaObject.type !== "IfcBuildingStorey") {
  1707. this.error("selectStorey() - Object is not an IfcBuildingStorey: '" + storeyObjectId + "'");
  1708. return;
  1709. }
  1710. this._storeysExplorer.selectStorey(storeyObjectId, done);
  1711. }
  1712.  
  1713. /**
  1714. * Saves viewer state to a BCF viewpoint.
  1715. *
  1716. * This does not save information about the project and model(s) that are currently loaded. When loading the viewpoint,
  1717. * the viewer will assume that the same project and models will be currently loaded (the BCF viewpoint specification
  1718. * does not contain that information).
  1719. *
  1720. * Note that xeokit's {@link Camera#look} is the **point-of-interest**, whereas the BCF ````camera_direction```` is a
  1721. * direction vector. Therefore, we save ````camera_direction```` as the vector from {@link Camera#eye} to {@link Camera#look}.
  1722. *
  1723. * @param {*} [options] Options for getting the viewpoint.
  1724. * @param {Boolean} [options.spacesVisible=false] Indicates whether ````IfcSpace```` types should be forced visible in the viewpoint.
  1725. * @param {Boolean} [options.openingsVisible=false] Indicates whether ````IfcOpening```` types should be forced visible in the viewpoint.
  1726. * @param {Boolean} [options.spaceBoundariesVisible=false] Indicates whether the boundaries of ````IfcSpace```` types should be visible in the viewpoint.
  1727. * @param {Boolean} [options.reverseClippingPlanes=false] When ````true````, clipping planes are reversed (https://github.com/buildingSMART/BCF-XML/issues/193)
  1728. * @param {Boolean} [options.defaultInvisible=false] When ````true````, will save the default visibility of all objects
  1729. * as ````false````. This means that when we load the viewpoint again, and there are additional models loaded that
  1730. * were not saved in the viewpoint, those models will be hidden when we load the viewpoint, and that only the
  1731. * objects in the viewpoint will be visible.
  1732. * @returns {*} BCF JSON viewpoint object
  1733. * @example
  1734. *
  1735. * const viewpoint = bimViewer.saveBCFViewpoint({
  1736. * spacesVisible: false, // Default
  1737. * spaceBoundariesVisible: false, // Default
  1738. * openingsVisible: false // Default
  1739. * });
  1740. *
  1741. * // viewpoint will resemble the following:
  1742. *
  1743. * {
  1744. * perspective_camera: {
  1745. * camera_view_point: {
  1746. * x: 0.0,
  1747. * y: 0.0,
  1748. * z: 0.0
  1749. * },
  1750. * camera_direction: {
  1751. * x: 1.0,
  1752. * y: 1.0,
  1753. * z: 2.0
  1754. * },
  1755. * camera_up_vector: {
  1756. * x: 0.0,
  1757. * y: 0.0,
  1758. * z: 1.0
  1759. * },
  1760. * field_of_view: 90.0
  1761. * },
  1762. * lines: [],
  1763. * clipping_planes: [{
  1764. * location: {
  1765. * x: 0.5,
  1766. * y: 0.5,
  1767. * z: 0.5
  1768. * },
  1769. * direction: {
  1770. * x: 1.0,
  1771. * y: 0.0,
  1772. * z: 0.0
  1773. * }
  1774. * }],
  1775. * bitmaps: [],
  1776. * snapshot: {
  1777. * snapshot_type: png,
  1778. * snapshot_data: "data:image/png;base64,......"
  1779. * },
  1780. * components: {
  1781. * visibility: {
  1782. * default_visibility: false,
  1783. * exceptions: [{
  1784. * ifc_guid: 4$cshxZO9AJBebsni$z9Yk,
  1785. * originating_system: xeokit.io,
  1786. * authoring_tool_id: xeokit/v1.0
  1787. * }]
  1788. * },
  1789. * selection: [{
  1790. * ifc_guid: "4$cshxZO9AJBebsni$z9Yk",
  1791. * }]
  1792. * }
  1793. * }
  1794. */
  1795. saveBCFViewpoint(options) {
  1796. return this._bcfViewpointsPlugin.getViewpoint(options);
  1797. }
  1798.  
  1799. /**
  1800. * Sets viewer state to the given BCF viewpoint.
  1801. *
  1802. * This assumes that the viewer currently contains the same project and model(s) that were loaded at the time that the
  1803. * viewpoint was originally saved (the BCF viewpoint specification does not contain that information).
  1804. *
  1805. * Note that xeokit's {@link Camera#look} is the **point-of-interest**, whereas the BCF ````camera_direction```` is a
  1806. * direction vector. Therefore, when loading a BCF viewpoint, we set {@link Camera#look} to the absolute position
  1807. * obtained by offsetting the BCF ````camera_view_point```` along ````camera_direction````.
  1808. *
  1809. * When loading a viewpoint, we also have the option to find {@link Camera#look} as the closest point of intersection
  1810. * (on the surface of any visible and pickable {@link Entity}) with a 3D ray fired from ````camera_view_point```` in
  1811. * the direction of ````camera_direction````.
  1812. *
  1813. * @param {*} bcfViewpoint BCF JSON viewpoint object or "reset" / "RESET" to reset the viewer, which clears SectionPlanes,
  1814. * shows default visible entities and restores camera to initial default position.
  1815. * @param {*} [options] Options for setting the viewpoint.
  1816. * @param {Boolean} [options.rayCast=true] When ````true```` (default), will attempt to set {@link Camera#look} to the closest
  1817. * point of surface intersection with a ray fired from the BCF ````camera_view_point```` in the direction of ````camera_direction````.
  1818. * @param {Boolean} [options.immediate=true] When ````true```` (default), immediately set camera position.
  1819. * @param {Boolean} [options.duration] Flight duration in seconds. Overrides {@link CameraFlightAnimation#duration}.
  1820. * @param {Boolean} [options.reset=true] When ````true```` (default), set {@link Entity#xrayed} and {@link Entity#highlighted} ````false```` on all scene objects.
  1821. * @param {Boolean} [options.reverseClippingPlanes=false] When ````true````, clipping planes are reversed (https://github.com/buildingSMART/BCF-XML/issues/193)
  1822. * @param {Boolean} [options.updateCompositeObjects=false] When ````true````, then when visibility and selection updates refer to composite objects (eg. an IfcBuildingStorey),
  1823. * then this method will apply the updates to objects within those composites.
  1824. */
  1825. loadBCFViewpoint(bcfViewpoint, options) {
  1826. if (!bcfViewpoint) {
  1827. this.error("loadBCFViewpoint() - Argument expected: bcfViewpoint");
  1828. return;
  1829. }
  1830. this._orthoMode.setActive(this.viewer.camera.projection === "ortho");
  1831. this._bcfViewpointsPlugin.setViewpoint(bcfViewpoint, options);
  1832. }
  1833.  
  1834. /**
  1835. * Resets the view.
  1836. *
  1837. * This resets object appearances (visibility, selection, highlight and X-ray), sets camera to
  1838. * default position, and removes section planes.
  1839. */
  1840. resetView() {
  1841. this._resetAction.reset();
  1842. }
  1843.  
  1844. /**
  1845. * Enables or disables the various buttons and controls throughout the viewer.
  1846. *
  1847. * This also makes various buttons appear disabled.
  1848. *
  1849. * @param {Boolean} enabled Whether or not to disable the controls.
  1850. */
  1851. setControlsEnabled(enabled) {
  1852.  
  1853. // Explorer
  1854.  
  1855. // Models tab is always enabled
  1856. this._objectsExplorer.setEnabled(enabled);
  1857. this._classesExplorer.setEnabled(enabled);
  1858. this._storeysExplorer.setEnabled(enabled);
  1859.  
  1860. // Toolbar
  1861.  
  1862. this._resetAction.setEnabled(enabled);
  1863. this._fitAction.setEnabled(enabled);
  1864. this._threeDMode.setEnabled(enabled);
  1865. this._orthoMode.setEnabled(enabled);
  1866. this._firstPersonMode.setEnabled(enabled);
  1867. this._queryTool.setEnabled(enabled);
  1868. this._hideTool.setEnabled(enabled);
  1869. this._selectionTool.setEnabled(enabled);
  1870. this._marqueeSelectionTool.setEnabled(enabled);
  1871. this._showSpacesMode.setEnabled(enabled);
  1872. this._sectionTool.setEnabled(enabled);
  1873.  
  1874. //
  1875. if (this._enablePropertiesInspector) {
  1876. this._propertiesInspector.setEnabled(enabled);
  1877. }
  1878. }
  1879.  
  1880. /**
  1881. * Sets whether or not keyboard camera control is enabled.
  1882. *
  1883. * This is useful when we don't want key events over the canvas to clash with other UI elements outside the canvas.
  1884. *
  1885. * Default value is ````true````.
  1886. *
  1887. * @param {Boolean} enabled Set ````true```` to enable keyboard input.
  1888. */
  1889. setKeyboardEnabled(enabled) {
  1890. this.viewer.scene.input.keyboardEnabled = enabled;
  1891. }
  1892.  
  1893. /**
  1894. * Gets whether keyboard camera control is enabled.
  1895. *
  1896. * This is useful when we don't want key events over the canvas to clash with other UI elements outside the canvas.
  1897. *
  1898. * Default value is ````true````.
  1899. *
  1900. * @returns {Boolean} Returns ````true```` if keyboard input is enabled.
  1901. */
  1902. getKeyboardEnabled() {
  1903. return this.viewer.scene.input.keyboardEnabled;
  1904. }
  1905.  
  1906. /**
  1907. * Clears sections.
  1908. *
  1909. * Sections are the slicing planes, that we use to section models in order to see interior structures.
  1910. */
  1911. clearSections() {
  1912. this._sectionTool.clear();
  1913. }
  1914.  
  1915. /**
  1916. * Disables all sections.
  1917. */
  1918. disableSections() {
  1919. this._sectionTool.disableSections();
  1920. }
  1921.  
  1922. /**
  1923. * Enables all sections.
  1924. */
  1925. enableSections() {
  1926. this._sectionTool.enableSections();
  1927. }
  1928.  
  1929. /**
  1930. * Inverts the direction of sections.
  1931. */
  1932. flipSections() {
  1933. this._sectionTool.flipSections();
  1934. }
  1935.  
  1936. /**
  1937. * Hides the section edition control, if currently shown.
  1938. */
  1939. hideSectionEditControl() {
  1940. this._sectionTool.hideControl();
  1941. }
  1942.  
  1943. /**
  1944. * Returns the number of sections that currently exist.
  1945. *
  1946. * sections are the sliceing planes, that we use to slice models in order to see interior structures.
  1947. *
  1948. * @returns {Number} The number of sections.
  1949. */
  1950. getNumSections() {
  1951. return this._sectionTool.getNumSections();
  1952. }
  1953.  
  1954. /**
  1955. * Destroys the viewer, freeing all resources.
  1956. */
  1957. destroy() {
  1958. this.viewer.destroy();
  1959. this._bcfViewpointsPlugin.destroy();
  1960. this._canvasContextMenu.destroy();
  1961. this._objectContextMenu.destroy();
  1962. }
  1963. }
  1964.  
  1965. export {BIMViewer};