UNPKG

hexa-viewer-communicator

Version:

A simple API for <hexa-viewer>

1,165 lines (1,085 loc) 39 kB
// Do not delete this as it allows importing the package with other projects import "regenerator-runtime/runtime.js"; import { Configurator } from "./configurator"; import { EventObservable } from "./event-observable"; import { IConfigurator } from "./interfaces/configurator.interface"; import { EventObservableTypes, IEventObservable, } from "./interfaces/event-observable.interface"; import { IPostMessage, IPostMessageOrigin, MaterializeMeshConfig, IViewerCommunicator, MeshData, IViewerCommunicatorOptions, ICreateInstanceFromUrlOptions, Collision, IImagesByTourResponse, ILight, IShadowPlaneOptions, IHdriOptions, IAdjustmentsPresetJson, IBroadcastSceneSummaryOption, ISceneSummary, IMeshProps, JsonToHtmlObject, EventSelector, TutorialType, IExportOptions, IExpotedModel, ExportFileType, IGifGenOptions, MediaFormat, IMatcapOptions, IApplyTextureOptions, TextureMimeType, IBoundingBox, IAnimationOptions, ICameraControlsStateAnimation, IMaterialPropsOptions, ISwapMaterialType, IDiamondOptions, } from "./interfaces/viewer-communicator.interface"; import { ImageToVideo } from "./image-to-video"; export class ViewerCommunicator implements IViewerCommunicator { public configurator: IConfigurator; private _hexaViewer: HTMLElement; private _frameID: string; private _isViewerLoaded: boolean; private _isModelLoaded: boolean; private _isAnimateEnterEnd: boolean; private _isViewerListening: boolean; // private _onGetMeshesData: Array<Function>; // private _onCollisions = [] as Array<Function>; private _onMessageBind: any; private _meshesData: { [id: string]: MeshData }; private _mesheAnimations: { [id: string]: IAnimationOptions }; private _xrSupport: boolean; private _eventObservable: IEventObservable; private _hasDestroyed: boolean; private _onLoadingProgress: Array<any>; constructor(options?: IViewerCommunicatorOptions) { this._hasDestroyed = false; options = options || {}; this._hexaViewer = options.hexaViewer; this._isViewerLoaded = false; this._isModelLoaded = false; this._isAnimateEnterEnd = false; this._onLoadingProgress = []; this.initFrameID(); this.attachEvents(); this.initEventObservable(); this.configurator = new Configurator(this); // Update the current viewer state. // In case it's too soon this won't even get to the viewer because he's not listening. // In case the viewer is already listening and a model has already been loaded the // await this.onModelLoaded() will return instead of never returning and preventing // all communicator functionality. this._updateViewerFullyLoaded(); } get hexaViewer() { return this._hexaViewer; } set hexaViewer(hv: HTMLElement) { this.attachInstance(hv); } get eventObservable() { return this._eventObservable; } get isModelLoaded() { return this._isViewerLoaded; } get hasDestroyed() { return this._hasDestroyed; } get isViewerListening() { return this._isViewerListening; } private set isViewerListening(value: boolean) { this._isViewerListening = value; if (this._isViewerListening) this._eventObservable.invoke( EventObservableTypes.ON_VIEWER_LISTENING, false ); } private set isModelLoaded(value: boolean) { if (value) this._onViewerFullyLoaded(); else this._isViewerLoaded = value; } private initEventObservable() { this._eventObservable = new EventObservable(); } private attachInstance(hexaViewer: HTMLElement) { this._hexaViewer = hexaViewer; this.initFrameID(); } private initFrameID(elem?: HTMLElement) { elem = elem || this._hexaViewer; if (elem) { this._frameID = elem.getAttribute("frame-id"); if (!this._frameID) { this._frameID = this.generateUUID(); elem.setAttribute("frame-id", this._frameID); } } } private attachEvents() { this._onMessageBind = this.onMessage.bind(this); self.addEventListener("message", this._onMessageBind, false); } private onMessage(event: MessageEvent) { const obj = this.safeParse(event.data) as IPostMessage; if (obj) { if (this._frameID && obj.id && this._frameID !== obj.id) return; // if (obj.to === IPostMessageOrigin.TOP) { switch (obj.action) { case "viewerListening": { this.isViewerListening = true; break; } case "viewerFullyLoaded": { this._onViewerFullyLoaded(); break; } case "onModelLoaded": { this._onModelLoaded(); break; } case "onAnimateEnterEnd": { this._onAnimateEnterEnd(); break; } case "setMeshesData": { this._onMeshesData(obj.data); break; } case "setMeshAnimations": { this._onMeshAnimations(obj.data); break; } case "setCollisions": { this._eventObservable.invoke( EventObservableTypes.ON_COLLISIONS, false, [obj.data] ); break; } case "setSceneSummary": { if (obj.data?.autoAdjustScene) this._eventObservable.invoke( EventObservableTypes.ON_ADJUDT_SCENE, true, [obj.data] ); this._eventObservable.invoke( EventObservableTypes.ON_SET_SCENE_SUMMARY, true, [obj.data] ); break; } case "setAttachJsonScene": { this._eventObservable.invoke( EventObservableTypes.ON_APPLY_PRESET, true ); break; } case "create_images_by_tour": { this._eventObservable.invoke( EventObservableTypes.ON_CREATE_IMAGES_BY_TOUR, true, [obj.data] ); break; } case "onLightsSummary": { this._eventObservable.invoke( EventObservableTypes.ON_LIGHTS_SUMMARY, true, [obj.data] ); break; } case "onConfiguratorSelectDone": { this._eventObservable.invoke( EventObservableTypes.ON_CONFIGURATOR_SELECT_DONE, true, [obj.data] ); break; } case "onModelInteraction": { this._eventObservable.invoke( EventObservableTypes.ON_MODEL_INTERACTION, false, [obj.data.type] ); break; } case "setViewerFullyLoaded": { this._eventObservable.invoke( EventObservableTypes.ON_SET_VIEWER_FULLY_LOADED, true, [obj.data] ); break; } case "setCurrentScreenshot": { this._eventObservable.invoke( EventObservableTypes.ON_SCREENSHOT, true, [obj.data] ); break; } case "onScreenshotsSequence": { this._eventObservable.invoke( EventObservableTypes.ON_SCREENSHOTS_SEQUENCE, true, [obj.data.images] ); break; } case "setModel": { this._eventObservable.invoke( EventObservableTypes.ON_EXPORT, true, [obj.data] ); break; } case "setBoundingBox": { this._eventObservable.invoke( EventObservableTypes.ON_BOUNDING_BOX, true, [obj.data] ); break; } case "setMaterials": { this._eventObservable.invoke( EventObservableTypes.ON_GET_MATERIALS, true, [obj.data] ); break; } case "setDiamondEffectOptions": { this._eventObservable.invoke( EventObservableTypes.ON_GET_DIAMONDS_OPTIONS, true, [obj.data] ); break; } case "setLoadingPercentage": { this._onLoadingProgress.forEach(f => f(obj.data)); break; } } // } } } private _checkModelLoaded() { if ( this._isViewerLoaded && (this._isModelLoaded || this._isAnimateEnterEnd) ) this._eventObservable.invoke( EventObservableTypes.ON_MODEL_LOADED, true ); } private _onAnimateEnterEnd() { this._isAnimateEnterEnd = true; this._checkModelLoaded(); this._eventObservable.invoke( EventObservableTypes.ON_ANIMATE_ENTER_END, true ); } private _onModelLoaded() { this._isModelLoaded = true; this._checkModelLoaded(); } private _onViewerFullyLoaded() { this._isModelLoaded = true; this._isViewerLoaded = true; this._eventObservable.invoke( EventObservableTypes.ON_VIEWER_LOADED, true ); this._checkModelLoaded(); } private _onMeshesData(obj: { [id: string]: MeshData }) { this._meshesData = obj; this.isModelLoaded = true; this._eventObservable.invoke( EventObservableTypes.ON_GET_MESHES_DATA, true, [this._meshesData] ); // if (this._meshesData) { // setTimeout(() =>{ // if (this._meshesData) { // Object.values(this._meshesData).forEach(md => { // if (md.rotation) { // md.rotationDegree = {} as ThreeVector3int; // md.rotationDegree.x = parseFloat(md.rotation.x.toString()) / (Math.PI / 180); // md.rotationDegree.y = parseFloat(md.rotation.y.toString()) / (Math.PI / 180); // md.rotationDegree.z = parseFloat(md.rotation.z.toString()) / (Math.PI / 180); // } // }); // } // }); // } } private _onMeshAnimations(obj: { [id: string]: IAnimationOptions }) { this._mesheAnimations = obj; this._eventObservable.invoke( EventObservableTypes.ON_GET_MESHE_ANIMATIONS, true, [this._mesheAnimations] ); } private _updateViewerFullyLoaded() { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_SET_VIEWER_FULLY_LOADED, (state: boolean) => { this._isViewerLoaded = state; if (this._isViewerLoaded) { this.onModelLoaded(); this._isViewerListening = true; this._onViewerFullyLoaded(); } resolve(); } ); const msg = { action: "broadcastViewerFullyLoaded", to: IPostMessageOrigin.WORKER, }; this.sendToViewer(msg); }); } onModelLoaded() { return new Promise((resolve: any, reject: any) => { if (this._isViewerLoaded) resolve(); else this._eventObservable.add( EventObservableTypes.ON_VIEWER_LOADED, resolve ); }); } // on model ready is a later event then onModelLoaded // it will fire after the enter animation is done (in case there is one) // in case there isn't it will fire next to the onModelLoaded onModelReady() { return new Promise((resolve: any, reject: any) => { if (this._isModelLoaded) resolve(); else this._eventObservable.add( EventObservableTypes.ON_MODEL_LOADED, resolve ); }); } onViewerListening() { return new Promise((resolve: any, reject: any) => { if (this._isViewerListening) resolve(); else this._eventObservable.add( EventObservableTypes.ON_VIEWER_LISTENING, resolve ); }); } onAnimateEnterEnd() { return new Promise((resolve: any, reject: any) => { if (this._isAnimateEnterEnd) resolve(); else this._eventObservable.add( EventObservableTypes.ON_ANIMATE_ENTER_END, resolve ); }); } getMeshesData(): Promise<{ [id: string]: MeshData }> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_GET_MESHES_DATA, resolve ); const msg = { action: "getMeshesData", to: IPostMessageOrigin.WORKER, }; this.sendToViewer(msg); }); } getMeshAnimations(): Promise<{ [id: string]: IAnimationOptions }> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_GET_MESHE_ANIMATIONS, resolve ); const msg = { action: "broadcastMeshAnimations", to: IPostMessageOrigin.WORKER, }; this.sendToViewer(msg); }); } getMaterials(): Promise<{ [id: string]: IAnimationOptions }> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_GET_MATERIALS, resolve ); const msg = { action: "broadcastMaterials", to: IPostMessageOrigin.WORKER, }; this.sendToViewer(msg); }); } updateMeshAnimations(meshAnimations: { [id: string]: IAnimationOptions }): void { this.sendToViewer({ action: "updateMeshAnimations", to: IPostMessageOrigin.WORKER, value: { meshAnimations } }); } private safeParse(p: any) { if (typeof p === "string") { try { return JSON.parse(p); } catch (e) { } } return p; } private generateUUID(): string { var d = new Date().getTime(); if ( window.performance && typeof window.performance.now === "function" ) { d += performance.now(); } var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, (c) => { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === "x" ? r : (r & 0x3) | 0x8).toString(16); } ); return uuid; } materializeMesh(meshName: string, config: MaterializeMeshConfig) { const msg = { action: "materializeMesh", to: IPostMessageOrigin.WORKER, value: { name: meshName, config: config, }, }; this.sendToViewer(msg); } sendToViewer(msg: IPostMessage) { // msg.from = IPostMessageOrigin.TOP; if (this._frameID) msg.id = this._frameID; self.postMessage(msg, location.origin); } // Creates a <hexa-viewer> instance from a viewer URL (typically the resource_default) async createInstanceFromUrl( viewerURL: string, params = {} as any, options?: ICreateInstanceFromUrlOptions ): Promise<HTMLElement> { options = options || {}; var hv = document.createElement("hexa-viewer"); var p = this.getUrlParams(viewerURL); var allP = Object.assign(p, params); if (typeof allP.server === "undefined") allP.server = "1"; if (typeof allP["frame-id"] === "undefined") allP["frame-id"] = this.generateUUID(); if (options.enableWebXR) { if (await this.isWebXrSupported()) allP["offscreen"] = "false"; } if (options.themeColor) allP["theme-color"] = options.themeColor; for (let i in allP) i && hv.setAttribute(i, allP[i] === null ? "" : allP[i]); this.initFrameID(hv); return hv; } private getUrlParams(url: string) { let queryString = {} as any, query = ""; if (url.indexOf("?") > -1) query = url.substring(url.indexOf("?") + 1); if (!query) return queryString; query = query.split("+").join(" "); let vars = query.split("&"); for (let i = 0; i < vars.length; i++) { let pair = vars[i].split("="); if (typeof queryString[pair[0]] === "undefined") { queryString[pair[0]] = pair[1] ? decodeURIComponent(pair[1]) : null; } else if (typeof queryString[pair[0]] === "string") { let arr = [queryString[pair[0]], decodeURIComponent(pair[1])]; queryString[pair[0]] = arr; } else { queryString[pair[0]].push(decodeURIComponent(pair[1])); } } return queryString; } togglePicInPic(state: boolean) { const msg = { action: "togglePicInPic", to: IPostMessageOrigin.MAIN, value: state, }; this.sendToViewer(msg); } toggleWireframe(state: boolean) { const msg = { action: "toggleWireframe", to: IPostMessageOrigin.WORKER, value: state, }; this.sendToViewer(msg); } toggleUvMode(state: boolean) { const msg = { action: "toggleUvMode", to: IPostMessageOrigin.WORKER, value: state, }; this.sendToViewer(msg); } toggleMatcapMode(state: boolean, options?: IMatcapOptions) { const msg = { action: "toggleMatcapMode", data: options, to: IPostMessageOrigin.WORKER, value: state, }; this.sendToViewer(msg); } toggleHideBottom(state: boolean) { const msg = { action: "toggleHideBottom", to: IPostMessageOrigin.WORKER, value: state, }; this.sendToViewer(msg); } isWebXrSupported(): Promise<boolean> { return new Promise((resolve: any, reject: any) => { if (typeof this._xrSupport === "boolean") { resolve(this._xrSupport); return; } const navigatorAny = navigator as any; if (!navigatorAny.xr || !navigatorAny.xr.isSessionSupported) { this._xrSupport = false; resolve(this._xrSupport); return; } try { navigatorAny.xr.isSessionSupported("immersive-ar").then( (res: boolean) => { this._xrSupport = res; resolve(this._xrSupport); }, (e: Error) => { this._xrSupport = false; resolve(this._xrSupport); } ); } catch (e) { this._xrSupport = false; resolve(this._xrSupport); } }); } toggleWebXR(state: boolean, invokeWhenReady = true) { const msg = { action: "toggleXr", obj: { state: state, value: invokeWhenReady, showIcon: true, }, } as IPostMessage; this.sendToViewer(msg); } toggleAR(state: boolean, invokeWhenReady = true) { const msg = { action: "toggleAR", obj: { state: state, value: invokeWhenReady }, } as IPostMessage; this.sendToViewer(msg); } waitForCollisions(): Promise<Array<Collision>> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_COLLISIONS, resolve ); }); } toggleCollision(collisionMode: boolean, color?: string) { this.sendToViewer({ action: "toggleCollision", to: IPostMessageOrigin.WORKER, value: { value: collisionMode, color, }, }); } deleteCollision(position: number, count: number) { this.sendToViewer({ action: "spliceCollision", to: IPostMessageOrigin.WORKER, value: { value: position, count, }, }); } removeAllCollisions() { this.sendToViewer({ action: "removeAllCollisions", to: IPostMessageOrigin.WORKER, }); } adjustScene() { return new Promise(async (resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_ADJUDT_SCENE, resolve ); await this.onModelLoaded(); this.sendToViewer({ action: "adjustScene", to: IPostMessageOrigin.WORKER, }); }); } // Not going to work without reloading instance or supporting each parameter on the viewer level // private applyPresetSrc(params: any): void { // for (let i in params) { // this.hexaViewer.setAttribute(i, params[i]); // } // } applyPreset(json: IAdjustmentsPresetJson) { return new Promise(async (resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_APPLY_PRESET, resolve ); // Make sure model has loaded await this.onModelLoaded(); this.sendToViewer({ action: "attachJsonScene", to: IPostMessageOrigin.WORKER, data: json, }); // // Make sure all viewer parameters has taken effect // await this.broadcastSceneSummary(); // if (preset?.preset_json) { // if (preset.preset_json.shadowPlane) // this.applyShadowPlane(preset.preset_json.shadowPlane); // if (preset.preset_json.hdri) { // if (!preset.preset_json.hdri.type) // preset.preset_json.hdri.intensity = 0; // this.applyHDRI(preset.preset_json.hdri); // } // if (preset.preset_json.lights) // await this.setLightsByJson(preset.preset_json.lights); // if (preset.preset_json.params) // this.assetAdjustmentsService.applyParams(); // } }); } applyHDRI(hdri: IHdriOptions) { this.sendToViewer({ action: "setHDR", to: IPostMessageOrigin.WORKER, value: hdri.type, options: hdri, }); } applyShadowPlane(shadowPlane: IShadowPlaneOptions) { this.sendToViewer({ action: "togglePlane", to: IPostMessageOrigin.WORKER, value: shadowPlane.opacity, options: { active: shadowPlane.active, color: shadowPlane.color, opacity: shadowPlane.opacity, physical: shadowPlane.physical, physicalOptions: shadowPlane.physicalOptions, reflector: shadowPlane.reflector, side: shadowPlane.side, }, }); } broadcastSceneSummary( params?: IBroadcastSceneSummaryOption ): Promise<ISceneSummary> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_SET_SCENE_SUMMARY, resolve ); this.sendToViewer({ action: "broadcastSceneSummary", to: IPostMessageOrigin.WORKER, value: params, }); }); } setLightsByJson(lights: { [id: string]: Array<ILight> }) { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_LIGHTS_SUMMARY, resolve ); this.sendToViewer({ action: "setLightsByJson", value: lights, to: IPostMessageOrigin.WORKER, }); }); } onCreateImagesByTour(): Promise<IImagesByTourResponse> { return new Promise(async (resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_CREATE_IMAGES_BY_TOUR, resolve ); }); } setMeshProps(optins: IMeshProps) { const obj = { name: optins.mesh.name, props: {}, } as any; // if (optins.key === 'rotation') { // // radians to degrees // optins.mesh.rotation.x = optins.mesh.rotationDegree.x * (Math.PI / 180); // optins.mesh.rotation.y = optins.mesh.rotationDegree.y * (Math.PI / 180); // optins.mesh.rotation.z = optins.mesh.rotationDegree.z * (Math.PI / 180); // optins.value = optins.mesh.rotation; // } obj["props"][optins.key] = optins.value; if (!this._meshesData[obj.name]) this._meshesData[obj.name] = {} as MeshData; (this._meshesData[obj.name] as any)[optins.key] = optins.value; this.sendToViewer({ action: "setMeshProps", to: IPostMessageOrigin.WORKER, value: obj, }); } appendDynamicElement( obj: JsonToHtmlObject, events: Array<EventSelector>, selectorToAppend = ".body" ) { this.sendToViewer({ action: "appendElement", to: IPostMessageOrigin.MAIN, obj: { obj, events, selectorToAppend, }, }); } controlsTutorial(types?: Array<TutorialType>) { this.sendToViewer({ action: "startZoomTutorial", to: IPostMessageOrigin.WORKER, value: types, }); } toggleAutoRotate(state: boolean) { this.sendToViewer({ action: "toggleAutoRotate", to: IPostMessageOrigin.WORKER, data: state, }); } onModelInteraction(cb?: Function): Promise<string> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_MODEL_INTERACTION, (type: string) => { if (cb) cb(type); resolve(type); } ); }); } goToInitialCamPos() { this.sendToViewer({ action: "goToInitialCamPos", to: IPostMessageOrigin.WORKER, }); } getCurrentScreenshot(): Promise<string> { return new Promise(async (resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_SCREENSHOT, resolve ); // await this.onModelReady(); await this.onAnimateEnterEnd(); this.sendToViewer({ action: "getCurrentScreenshot", to: IPostMessageOrigin.WORKER, }); }); } expotModel(options: IExportOptions): Promise<IExpotedModel> { return new Promise(async (resolve: any, reject: any) => { if (!options.downloadFile) this._eventObservable.add( EventObservableTypes.ON_EXPORT, resolve ); await this.onModelReady(); let action = "", value = null; switch (options.type) { case ExportFileType.GLB: case ExportFileType.glTF: { action = "broadcastGLTF"; value = { binary: options.type === ExportFileType.GLB, trs: false, onlyVisible: true, truncateDrawRange: false, embedImages: true, // animations: [] forceIndices: true, forcePowerOfTwoTextures: true, imagesFileType: null, normalImagesFileType: null, mroImagesFileType: null, imagesCompressionFactor: null, downloadFile: options.downloadFile, maxTexturesSize: null, maxDiffuseTexturesSize: null, maxNormalTexturesSize: null, maxMroTexturesSize: null, optipng: options.compressPNG, simplify: false, deleteUV2: false, }; break; } case ExportFileType.USDZ: { action = "broadcastUSDZ"; break; } case ExportFileType.OBJ: { action = "broadcastOBJ"; break; } default: { action = "broadcastGLTF"; break; } } this.sendToViewer({ action, value, to: IPostMessageOrigin.WORKER, }); if (options.downloadFile) resolve(null); }); } getScreenshotsSequence( options?: IGifGenOptions ): Promise<Array<string> | Blob> { return new Promise(async (resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_SCREENSHOTS_SEQUENCE, async (images: Array<string>) => { if (options.format === MediaFormat.VIDEO) { const i2v = new ImageToVideo(images); if (options.codec) i2v.codecs = options.codec; resolve((await i2v.getVideo()) as Blob); } else resolve(images); } ); await this.onModelReady(); await this.onAnimateEnterEnd(); const finalOptions = {} as any; if (options) { finalOptions.ggNumOfFrames = options.numOfFrames; // finalOptions.ggInterval = options.interval; // finalOptions.ggSampleInterval = options.sampleInterval; // finalOptions.ggRotateSpeen = options.rotateSpeen; } finalOptions.msgObj = { data: {}, action: "onScreenshotsSequence", to: IPostMessageOrigin.TOP, from: IPostMessageOrigin.WORKER, }; this.sendToViewer({ action: "broadcastGifSequence", to: IPostMessageOrigin.WORKER, data: { options: finalOptions, }, }); }); } applyTexture(options: IApplyTextureOptions) { this.sendToViewer({ action: "mapTextureToMaterial", to: IPostMessageOrigin.WORKER, value: { texture: options.src, material: options.materialName, options: { map: options.mapType, intensity: options.intensity, textureSrc: options.src, srcChange: !!options.src, color: options.color, videoSrc: options.mimeType === TextureMimeType.VIDEO ? options.src : null, }, }, }); } setMaterialProps(options: IMaterialPropsOptions) { this.sendToViewer({ action: "setMaterialProps", to: IPostMessageOrigin.WORKER, value: { name: options.materialName, props: options.props }, }); } swapMaterialType(options: ISwapMaterialType) { this.sendToViewer({ action: "swapMaterialType", to: IPostMessageOrigin.WORKER, value: { name: options.materialName, newType: options.type }, }); } displayFiles(files: Array<Blob>) { this.sendToViewer({ action: "displayFiles", to: IPostMessageOrigin.MAIN, value: { files: files, }, }); } getBoundingBox(): Promise<IBoundingBox> { return new Promise(async (resolve: any, reject: any) => { await this.onModelReady(); this._eventObservable.add( EventObservableTypes.ON_BOUNDING_BOX, resolve ); this.sendToViewer({ action: "broadcastBoundingBox", to: IPostMessageOrigin.WORKER, }); }); } async toggleNoDistanceLimit(state: boolean) { await this.onModelReady(); this.sendToViewer({ action: "toggleNoDistanceLimit", to: IPostMessageOrigin.WORKER, value: state }); } async toggleCloseup(state: boolean) { await this.onModelReady(); this.sendToViewer({ action: "toggleCloseup", to: IPostMessageOrigin.WORKER, value: state }); } async setCameraPosition(pos: ICameraControlsStateAnimation) { await this.onModelReady(); this.sendToViewer({ action: "setCameraPosition", to: IPostMessageOrigin.WORKER, value: pos }); } async setDiamonds(state: boolean, meshesNames: Array<string>, options: IDiamondOptions) { await this.onModelReady(); this.sendToViewer({ action: "toggleDiamond", to: IPostMessageOrigin.WORKER, value: { state, meshes: meshesNames, options } }); } getDiamondsOptions(): Promise<IDiamondOptions> { return new Promise((resolve: any, reject: any) => { this._eventObservable.add( EventObservableTypes.ON_GET_DIAMONDS_OPTIONS, resolve ); const msg = { action: "broadcastDiamondEffectOptions", to: IPostMessageOrigin.WORKER, }; this.sendToViewer(msg); }); } registerForLoadingProgress(callback: Function | any) { this._onLoadingProgress.push(callback); } destroy() { this._hasDestroyed = true; this._eventObservable.destroy(); this._isViewerListening = false; this._isViewerLoaded = false; this._isModelLoaded = false; this._isAnimateEnterEnd = false; this._onLoadingProgress = []; self.removeEventListener("message", this._onMessageBind, false); } }