| dist | dist | ||||
| docs | docs | ||||
| ./index.html | |||||
| /index.html | |||||
| examples/**/*.js | examples/**/*.js | ||||
| examples/**/*.js.map | examples/**/*.js.map | ||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Dropzone Plugin</title> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| #prompt-div{ | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 50%; | |||||
| transform: translate(-50%, -50%); | |||||
| color: #333; | |||||
| font-size: 2em; | |||||
| font-family: sans-serif; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| <div id="prompt-div">Drop any <span style="font-family: monospace">glb/hdr/png/jpg</span> files here</div> | |||||
| </div> | |||||
| </body> |
| import {_testFinish, DropzonePlugin, ThreeViewer} from 'threepipe' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| dropzone: { // this can also be set to true and configured by getting a reference to the DropzonePlugin | |||||
| allowedExtensions: ['gltf', 'glb', 'hdr', 'png', 'jpg', 'json', 'fbx', 'obj'], // only allow these file types. If undefined, all files are allowed. | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, // auto dispose of old scene objects | |||||
| autoSetEnvironment: true, // when hdr is dropped | |||||
| autoSetBackground: true, // when any image is dropped | |||||
| autoCenter: true, // auto center the object | |||||
| autoScale: true, // auto scale according to radius | |||||
| autoScaleRadius: 2, | |||||
| license: 'Imported from dropzone', // Any license to set on imported objects | |||||
| importConfig: true, // import config from file | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const dropzone = viewer.getPlugin(DropzonePlugin) | |||||
| dropzone?.addEventListener('drop', (e: any) => { | |||||
| if (!e.assets?.length) return // no assets imported | |||||
| console.log('Dropped Event:', e) | |||||
| const promptDiv = document.getElementById('prompt-div')! | |||||
| promptDiv.style.display = 'none' | |||||
| }) | |||||
| } | |||||
| init().then(_testFinish) |
| import {_testFinish, ThreeViewer} from 'threepipe' | import {_testFinish, ThreeViewer} from 'threepipe' | ||||
| const viewer = new ThreeViewer({canvas: document.getElementById('mcanvas') as HTMLCanvasElement, msaa: true}) | |||||
| async function init() { | async function init() { | ||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| dropzone: { | |||||
| allowedExtensions: ['gltf', 'glb', 'hdr'], | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| autoSetEnvironment: true, // when hdr is dropped | |||||
| autoSetBackground: true, | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||||
| setBackground: true, | |||||
| }) | |||||
| const result = await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | const result = await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | ||||
| autoCenter: true, | autoCenter: true, | ||||
| autoScale: true, | autoScale: true, |
| <ul> | <ul> | ||||
| <li><a href="./depth-buffer-plugin">Depth Buffer Plugin </a></li> | <li><a href="./depth-buffer-plugin">Depth Buffer Plugin </a></li> | ||||
| <li><a href="./render-target-preview">Render Target Preview </a></li> | <li><a href="./render-target-preview">Render Target Preview </a></li> | ||||
| <li><a href="./dropzone-plugin">Dropzone (Drag & Drop) </a></li> | |||||
| </ul> | </ul> | ||||
| <h2 class="category">Import/Export</h2> | <h2 class="category">Import/Export</h2> | ||||
| <ul> | <ul> |
| } | } | ||||
| export {MTLLoader2}; | |||||
| export {MTLLoader2, type MaterialCreator}; |
| export {JSONMaterialLoader} from './JSONMaterialLoader' | export {JSONMaterialLoader} from './JSONMaterialLoader' | ||||
| export {SimpleJSONLoader} from './SimpleJSONLoader' | export {SimpleJSONLoader} from './SimpleJSONLoader' | ||||
| export {MTLLoader2} from './MTLLoader2' | |||||
| export {MTLLoader2, type MaterialCreator} from './MTLLoader2' | |||||
| export {OBJLoader2} from './OBJLoader2' | export {OBJLoader2} from './OBJLoader2' | ||||
| export {ZipLoader} from './ZipLoader' | export {ZipLoader} from './ZipLoader' | ||||
| export {GLTFLoader2} from './GLTFLoader2' | export {GLTFLoader2} from './GLTFLoader2' |
| import {ITexture} from './ITexture' | import {ITexture} from './ITexture' | ||||
| export interface AddObjectOptions { | export interface AddObjectOptions { | ||||
| addToRoot?: boolean // default = false | |||||
| // TODO; add more options | |||||
| /** | |||||
| * Add directly to the {@link RootScene} object instead of {@link RootScene.modelRoot} | |||||
| * @default false | |||||
| */ | |||||
| addToRoot?: boolean | |||||
| /** | |||||
| * Automatically center the object in the scene. | |||||
| * @default false | |||||
| */ | |||||
| autoCenter?: boolean, | autoCenter?: boolean, | ||||
| /** | |||||
| * Add a license to the object | |||||
| */ | |||||
| license?: string, | license?: string, | ||||
| /** | |||||
| * Automatically scale the object according to its bounding box and the {@link autoScaleRadius} setting | |||||
| * @default false | |||||
| */ | |||||
| autoScale?: boolean | autoScale?: boolean | ||||
| autoScaleRadius?: number // default = 2 | |||||
| /** | |||||
| * Radius to use for {@link autoScale} | |||||
| * @default 2 | |||||
| */ | |||||
| autoScaleRadius?: number | |||||
| /** | |||||
| * any attached viewer config will be ignored if this is set to true | |||||
| * @default true | |||||
| */ | |||||
| importConfig?: boolean | |||||
| /** | |||||
| * Clear the viewer scene objects before the new object is added. Same as {@link disposeSceneObjects} but does not dispose the objects. | |||||
| */ | |||||
| clearSceneObjects?: boolean | |||||
| /** | |||||
| * Dispose all the scene objects before the new object is added. Same as {@link clearSceneObjects} but also disposes the objects. | |||||
| */ | |||||
| disposeSceneObjects?: boolean | |||||
| importConfig?: boolean // any attached viewer config will be ignored if this is set to true | |||||
| // TODO; add more options | |||||
| } | } | ||||
| // | string | // | string | ||||
| // change?: string | // change?: string | ||||
| } | } | ||||
| export type ISceneSetDirtyOptions = IObjectSetDirtyOptions & { | export type ISceneSetDirtyOptions = IObjectSetDirtyOptions & { | ||||
| [key: string]: any | |||||
| } | } | ||||
| * @param options | * @param options | ||||
| */ | */ | ||||
| addObject<T extends IObject3D|Object3D = IObject3D>(imported: T, options?: AddObjectOptions): T { | addObject<T extends IObject3D|Object3D = IObject3D>(imported: T, options?: AddObjectOptions): T { | ||||
| if (options?.clearSceneObjects || options?.disposeSceneObjects) { | |||||
| this.clearSceneModels(options.disposeSceneObjects) | |||||
| } | |||||
| if (!imported) return imported | if (!imported) return imported | ||||
| if (!imported.isObject3D) { | if (!imported.isObject3D) { | ||||
| console.error('Invalid object, cannot add to scene.', imported) | console.error('Invalid object, cannot add to scene.', imported) | ||||
| * @param options | * @param options | ||||
| */ | */ | ||||
| loadModelRoot(obj: RootSceneImportResult, options?: AddObjectOptions) { | loadModelRoot(obj: RootSceneImportResult, options?: AddObjectOptions) { | ||||
| if (options?.clearSceneObjects || options?.disposeSceneObjects) { | |||||
| this.clearSceneModels(options.disposeSceneObjects) | |||||
| } | |||||
| if (!obj.userData?.rootSceneModelRoot) { | if (!obj.userData?.rootSceneModelRoot) { | ||||
| console.error('Invalid model root scene object. Trying to add anyway.', obj) | console.error('Invalid model root scene object. Trying to add anyway.', obj) | ||||
| } | } | ||||
| this.setDirty({refreshScene: true}) | this.setDirty({refreshScene: true}) | ||||
| } | } | ||||
| clearSceneModels(dispose = false): void { | |||||
| if (dispose) this.disposeSceneModels() | |||||
| clearSceneModels(dispose = false, setDirty = true): void { | |||||
| if (dispose) return this.disposeSceneModels(setDirty) | |||||
| this.modelRoot.clear() | this.modelRoot.clear() | ||||
| this.modelRoot.children = [] | this.modelRoot.children = [] | ||||
| this.setDirty({refreshScene: true}) | |||||
| setDirty && this.setDirty({refreshScene: true}) | |||||
| } | } | ||||
| disposeSceneModels(setDirty = true) { | disposeSceneModels(setDirty = true) { | ||||
| public backgroundColor: Color | null = null // read in three.js WebGLBackground | public backgroundColor: Color | null = null // read in three.js WebGLBackground | ||||
| /** | /** | ||||
| * @deprecated Use addSceneObject | |||||
| * @deprecated Use {@link addObject} | |||||
| */ | */ | ||||
| add(...object: Object3D[]): this { | add(...object: Object3D[]): this { | ||||
| super.add(...object) | super.add(...object) |
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | ||||
| export {PipelinePassPlugin} from './base/PipelinePassPlugin' | export {PipelinePassPlugin} from './base/PipelinePassPlugin' | ||||
| export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | ||||
| export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' |
| import {AViewerPluginSync} from '../../viewer/AViewerPlugin' | |||||
| import {type ThreeViewer} from '../../viewer/' | |||||
| import {Dropzone} from '../../utils' | |||||
| import {uiButton, uiConfig, uiFolderContainer, uiToggle} from 'uiconfig.js' | |||||
| import type {AddAssetOptions, ImportFilesOptions, ImportResult} from '../../assetmanager' | |||||
| export interface DropzonePluginOptions { | |||||
| domElement?: HTMLElement | |||||
| allowedExtensions?: string[] | |||||
| autoImport?: boolean | |||||
| autoAdd?: boolean | |||||
| importOptions?: ImportFilesOptions | |||||
| addOptions?: AddAssetOptions | |||||
| } | |||||
| @uiFolderContainer('Dropzone') | |||||
| export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||||
| static readonly PluginType = 'Dropzone' | |||||
| @uiToggle() enabled = true | |||||
| private _inputEl?: HTMLInputElement | |||||
| private _dropzone?: Dropzone | |||||
| private _allowedExtensions: string[]|undefined = undefined // undefined and empty array is different. | |||||
| /** | |||||
| * Automatically import assets when dropped. | |||||
| */ | |||||
| autoImport = true | |||||
| /** | |||||
| * Automatically add dropped and imported assets to the scene. | |||||
| * Works only if {@link autoImport} is true. | |||||
| */ | |||||
| @uiToggle() autoAdd = true | |||||
| /** | |||||
| * Import options for the {@link AssetImporter.importFiles} | |||||
| */ | |||||
| @uiConfig() importOptions: ImportFilesOptions = { | |||||
| autoImportZipContents: true, | |||||
| forceImporterReprocess: false, | |||||
| } | |||||
| /** | |||||
| * Add options for the {@link RootScene.addObject} | |||||
| */ | |||||
| @uiConfig() addOptions: AddAssetOptions = { | |||||
| autoCenter: true, | |||||
| importConfig: true, | |||||
| autoScale: true, | |||||
| autoScaleRadius: 2, | |||||
| license: '', | |||||
| clearSceneObjects: false, | |||||
| disposeSceneObjects: false, | |||||
| autoSetBackground: false, | |||||
| autoSetEnvironment: true, | |||||
| } | |||||
| /** | |||||
| * Allowed file extensions. If undefined, all files are allowed. | |||||
| */ | |||||
| get allowedExtensions(): string[] | undefined { | |||||
| return this._allowedExtensions | |||||
| } | |||||
| set allowedExtensions(value: string[] | undefined) { | |||||
| this._allowedExtensions = value | |||||
| if (this._inputEl) this._inputEl.accept = value ? value.map(v=>'.' + v).join(', ') : '' | |||||
| } | |||||
| /** | |||||
| * Prompt for file selection using the browser file dialog. | |||||
| */ | |||||
| @uiButton('Select files') | |||||
| public promptForFile(): void { | |||||
| if (!this.enabled) return | |||||
| this.allowedExtensions = this._allowedExtensions | |||||
| this._inputEl?.click() | |||||
| } | |||||
| private _domElement?: HTMLElement | |||||
| constructor(options?: DropzonePluginOptions) { | |||||
| super() | |||||
| if (!options) return | |||||
| this._domElement = options.domElement | |||||
| this.allowedExtensions = options.allowedExtensions | |||||
| this.autoImport = options.autoImport ?? this.autoImport | |||||
| this.autoAdd = options.autoAdd ?? this.autoAdd | |||||
| this.importOptions = {...this.importOptions, ...options.importOptions} | |||||
| this.addOptions = {...this.addOptions, ...options.addOptions} | |||||
| } | |||||
| onAdded(viewer: ThreeViewer) { | |||||
| super.onAdded(viewer) | |||||
| this._inputEl = document.createElement('input')! | |||||
| this._inputEl.type = 'file' | |||||
| this._dropzone = new Dropzone(this._domElement || viewer.canvas, this._inputEl, { | |||||
| drop: this._onFileDrop.bind(this), | |||||
| }) | |||||
| this.allowedExtensions = this._allowedExtensions | |||||
| } | |||||
| onRemove(viewer: ThreeViewer) { | |||||
| super.onRemove(viewer) | |||||
| this._dropzone?.destroy() | |||||
| this._dropzone = undefined | |||||
| this._inputEl = undefined | |||||
| } | |||||
| private async _onFileDrop({files}: {files: Map<string, File>}&any) { | |||||
| if (!files) return | |||||
| if (!this.enabled) return | |||||
| const viewer = this._viewer | |||||
| if (!viewer) return | |||||
| if (this._allowedExtensions !== undefined) { | |||||
| for (const file of files.keys()) { | |||||
| if (!this._allowedExtensions.includes(file.split('.').pop()?.toLowerCase() ?? '')) { | |||||
| files.delete(file) | |||||
| } | |||||
| } | |||||
| } | |||||
| if (files.size < 1) return | |||||
| const manager = viewer.assetManager | |||||
| let imported: Map<string, (ImportResult | undefined)[]>|undefined | |||||
| let assets: (ImportResult | undefined)[]|undefined | |||||
| if (this.autoImport) { | |||||
| imported = await manager.importer.importFiles(files, { | |||||
| allowedExtensions: this.allowedExtensions, ...this.importOptions, | |||||
| }) | |||||
| if (this.autoAdd) { | |||||
| const toAdd = [...imported?.values() ?? []].flat(2).filter(v=>!!v) ?? [] | |||||
| assets = await manager.loadImported(toAdd, {...this.addOptions}) | |||||
| } | |||||
| } | |||||
| this.dispatchEvent({type: 'drop', files, imported, assets}) | |||||
| } | |||||
| } |
| /** | |||||
| * Fork of: https://github.com/donmccurdy/simple-dropzone updated: Mar 2021 | |||||
| * The MIT License (MIT) | |||||
| * Copyright (c) 2018 Don McCurdy | |||||
| * | |||||
| * Changes: | |||||
| * Convert to typescript. | |||||
| * webkitRelativePath for file input select. | |||||
| * Removed unzip and dependency(done in importer). | |||||
| * | |||||
| * Watches an element for file drops, parses to create a filemap hierarchy, | |||||
| * and emits the result. | |||||
| */ | |||||
| export class Dropzone { | |||||
| get inputEl(): HTMLInputElement|undefined { | |||||
| return this._inputEl | |||||
| } | |||||
| get el(): HTMLElement|undefined { | |||||
| return this._el | |||||
| } | |||||
| private _el?: HTMLElement | |||||
| private _inputEl?: HTMLInputElement | |||||
| private _listeners: Record<DropEventType, ListenerCallback[]> | |||||
| constructor(el?: HTMLElement, inputEl?: HTMLInputElement, listeners?: Partial<Record<DropEventType, ListenerCallback>>) { | |||||
| this._el = el | |||||
| this._inputEl = inputEl | |||||
| this._listeners = { | |||||
| drop: [], | |||||
| dropstart: [], | |||||
| droperror: [], | |||||
| } | |||||
| this._onDragover = this._onDragover.bind(this) | |||||
| this._onDrop = this._onDrop.bind(this) | |||||
| this._onSelect = this._onSelect.bind(this) | |||||
| el?.addEventListener('dragover', this._onDragover, false) | |||||
| el?.addEventListener('drop', this._onDrop, false) | |||||
| inputEl?.addEventListener('change', this._onSelect) | |||||
| listeners && Object.entries(listeners).forEach(([e, c])=> c && this.on(e as DropEventType, c)) | |||||
| } | |||||
| on(type: DropEventType, callback: ListenerCallback): Dropzone { | |||||
| this._listeners[type].push(callback) | |||||
| return this | |||||
| } | |||||
| private _emit(type: DropEventType, data?: {[id:string]: any}) { | |||||
| this._listeners[type] | |||||
| .forEach((callback) => callback(data)) | |||||
| return this | |||||
| } | |||||
| /** | |||||
| * Destroys the instance. | |||||
| */ | |||||
| destroy(): void { | |||||
| const el = this._el | |||||
| const inputEl = this._inputEl | |||||
| el?.removeEventListener('dragover', this._onDragover) | |||||
| el?.removeEventListener('drop', this._onDrop) | |||||
| inputEl?.removeEventListener('change', this._onSelect) | |||||
| } | |||||
| /** | |||||
| * References (and horror): | |||||
| * - https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/items | |||||
| * - https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/files | |||||
| * - https://code.flickr.net/2012/12/10/drag-n-drop/ | |||||
| * - https://stackoverflow.com/q/44842247/1314762 | |||||
| * | |||||
| */ | |||||
| private _onDrop(e: DragEvent) { | |||||
| e.stopPropagation() | |||||
| e.preventDefault() | |||||
| this._emit('dropstart') | |||||
| const files = Array.from(e.dataTransfer?.files || []) as DropFile[] | |||||
| const items = Array.from(e.dataTransfer?.items || []) | |||||
| if (files.length === 0 && items.length === 0) { | |||||
| this._fail('Required drag-and-drop APIs are not supported in this browser.') | |||||
| return | |||||
| } | |||||
| // Prefer .items, which allow folder traversal if necessary. | |||||
| if (items.length > 0) { | |||||
| const entries = items.map((item) => item.webkitGetAsEntry()) | |||||
| // if (entries[0].name.match(/\.zip$/)) { | |||||
| // this._loadZip(items[0].getAsFile()) | |||||
| // } else { | |||||
| this._loadNextEntry(new Map(), entries) | |||||
| // } | |||||
| return | |||||
| } | |||||
| // Fall back to .files, since folders can't be traversed. | |||||
| // if (files.length === 1 && files[0].name.match(/\.zip$/)) { | |||||
| // this._loadZip(files[0]) | |||||
| // } | |||||
| this._emit('drop', {files: new Map(files.map((file) => { | |||||
| file.filePath = file.name | |||||
| return [file.filePath, file] | |||||
| }))}) | |||||
| } | |||||
| /** | |||||
| * @param {Event} e | |||||
| */ | |||||
| private _onDragover(e: DragEvent) { | |||||
| e.stopPropagation() | |||||
| e.preventDefault() | |||||
| e.dataTransfer && (e.dataTransfer.dropEffect = 'copy') // Explicitly show this is a copy. | |||||
| } | |||||
| private _onSelect(e: Event) { | |||||
| if (!this._inputEl) { | |||||
| console.warn('Invalid Dropzone event ', e) | |||||
| return | |||||
| } | |||||
| this._emit('dropstart') | |||||
| // HTML file inputs do not seem to support folders, so assume this is a flat file list. | |||||
| const files: DropFile[] = [].slice.call(this._inputEl.files ?? new FileList()) | |||||
| // Automatically decompress a zip archive if it is the only file given. | |||||
| // if (files.length === 1 && this._isZip(files[0])) { | |||||
| // this._loadZip(files[0]) | |||||
| // return | |||||
| // } | |||||
| const fileMap = new Map() | |||||
| files.forEach((file) => { | |||||
| file.filePath = (file as any).webkitRelativePath || file.name | |||||
| fileMap.set(file.filePath, file) | |||||
| }) | |||||
| this._emit('drop', {files: fileMap}) | |||||
| } | |||||
| /** | |||||
| * Iterates through a list of FileSystemEntry objects, creates the fileMap | |||||
| * tree, and emits the result. | |||||
| * @param {Array<FileSystemEntry>} entries | |||||
| */ | |||||
| private _loadNextEntry(fileMap: Map<string, DropFile>, entries: any[]) { | |||||
| const entry = entries.pop() | |||||
| if (!entry) { | |||||
| this._emit('drop', {files: fileMap}) | |||||
| return | |||||
| } | |||||
| if (entry.isFile) { | |||||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | |||||
| entry.file((file: DropFile) => { | |||||
| file.filePath = entry.fullPath | |||||
| fileMap.set(entry.fullPath, file) | |||||
| this._loadNextEntry(fileMap, entries) | |||||
| }, () => console.error('Could not load file: %s', entry.fullPath)) | |||||
| } else if (entry.isDirectory) { | |||||
| // readEntries() must be called repeatedly until it stops returning results. | |||||
| // https://www.w3.org/TR/2012/WD-file-system-api-20120417/#the-directoryreader-interface | |||||
| // https://bugs.chromium.org/p/chromium/issues/detail?id=378883 | |||||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | |||||
| const reader = entry.createReader() | |||||
| const readerCallback = (newEntries: any[]) => { | |||||
| if (newEntries.length) { | |||||
| entries = entries.concat(newEntries) | |||||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | |||||
| reader.readEntries(readerCallback) | |||||
| } else { | |||||
| this._loadNextEntry(fileMap, entries) | |||||
| } | |||||
| } | |||||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | |||||
| reader.readEntries(readerCallback) | |||||
| } else { | |||||
| console.warn('Unknown asset type: ' + entry.fullPath) | |||||
| this._loadNextEntry(fileMap, entries) | |||||
| } | |||||
| } | |||||
| // /** | |||||
| // * Inflates a File in .ZIP format, creates the fileMap tree, and emits the | |||||
| // * result. | |||||
| // * @param {File} file | |||||
| // */ | |||||
| // _loadZip(file) { | |||||
| // const pending = [] | |||||
| // const fileMap = new Map() | |||||
| // const archive = new fs.FS() | |||||
| // | |||||
| // const traverse = (node) => { | |||||
| // if (node.directory) { | |||||
| // node.children.forEach(traverse) | |||||
| // } else if (node.name[0] !== '.') { | |||||
| // pending.push(new Promise((resolve) => { | |||||
| // node.getData(new zip.BlobWriter(), (blob) => { | |||||
| // blob.name = node.name | |||||
| // fileMap.set(node.getFullname(), blob) | |||||
| // resolve() | |||||
| // }) | |||||
| // })) | |||||
| // } | |||||
| // } | |||||
| // | |||||
| // archive.importBlob(file, () => { | |||||
| // traverse(archive.root) | |||||
| // Promise.all(pending).then(() => { | |||||
| // this._emit('drop', {files: fileMap, archive: file}) | |||||
| // }) | |||||
| // }) | |||||
| // } | |||||
| // /** | |||||
| // * @param {File} file | |||||
| // * @return {Boolean} | |||||
| // */ | |||||
| // _isZip(file) { | |||||
| // return file.type === 'application/zip' || file.name.match(/\.zip$/) | |||||
| // } | |||||
| /** | |||||
| * @throws | |||||
| */ | |||||
| private _fail(message: string) { | |||||
| this._emit('droperror', {message: message}) | |||||
| } | |||||
| } | |||||
| export type DropEventType = 'drop'|'dropstart'|'droperror' | |||||
| export type ListenerCallback = ((data?:{files?:Map<string, DropFile>, message?:string})=>void) | |||||
| export interface DropFile extends File{ | |||||
| filePath: string | |||||
| } |
| export * from './browser-helpers' | export * from './browser-helpers' | ||||
| export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | ||||
| export {GLStatsJS} from './GLStatsJS' | export {GLStatsJS} from './GLStatsJS' | ||||
| export {Dropzone, type DropFile, type ListenerCallback, type DropEventType} from './Dropzone' | |||||
| export {ThreeSerialization, type SerializationMetaType, type SerializationResourcesType, MetaImporter} from './serialization' | export {ThreeSerialization, type SerializationMetaType, type SerializationResourcesType, MetaImporter} from './serialization' | ||||
| export {shaderReplaceString} from './shader-helpers' | export {shaderReplaceString} from './shader-helpers' |
| } from '../assetmanager' | } from '../assetmanager' | ||||
| import {GLStatsJS, IDialogWrapper, windowDialogWrapper} from '../utils' | import {GLStatsJS, IDialogWrapper, windowDialogWrapper} from '../utils' | ||||
| import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | ||||
| import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | |||||
| export type IViewerEvent = BaseEvent & { | export type IViewerEvent = BaseEvent & { | ||||
| type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin' | type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin' | ||||
| * Options for the ThreeViewer creation. | * Options for the ThreeViewer creation. | ||||
| * @category Viewer | * @category Viewer | ||||
| */ | */ | ||||
| export interface ThreeViewerOptions extends AssetManagerOptions{ | |||||
| export interface ThreeViewerOptions { | |||||
| /** | /** | ||||
| * The canvas element to use for rendering. Only one of container and canvas must be specified. | * The canvas element to use for rendering. Only one of container and canvas must be specified. | ||||
| */ | */ | ||||
| debug?: boolean | debug?: boolean | ||||
| assetManager?: AssetManagerOptions | |||||
| /** | |||||
| * Add the dropzone plugin to the viewer, allowing to drag and drop files into the viewer over the canvas/container. | |||||
| * Set to true/false to enable/disable the plugin, or pass options to configure the plugin. Assuming true if options are passed. | |||||
| * @default - false | |||||
| */ | |||||
| dropzone?: boolean|DropzonePluginOptions | |||||
| /** | /** | ||||
| * @deprecated use {@link msaa} instead | * @deprecated use {@link msaa} instead | ||||
| */ | */ | ||||
| this.setDirty(this.renderManager, e) | this.setDirty(this.renderManager, e) | ||||
| }) | }) | ||||
| this.assetManager = new AssetManager(this, options) | |||||
| this.assetManager = new AssetManager(this, options.assetManager) | |||||
| if (this.resizeObserver) this.resizeObserver.observe(this._canvas) | if (this.resizeObserver) this.resizeObserver.observe(this._canvas) | ||||
| // sometimes resize observer is late, so extra check | // sometimes resize observer is late, so extra check | ||||
| this._canvas.addEventListener('webglcontextrestored', this._onContextRestore, false) | this._canvas.addEventListener('webglcontextrestored', this._onContextRestore, false) | ||||
| this._canvas.addEventListener('webglcontextlost', this._onContextLost, false) | this._canvas.addEventListener('webglcontextlost', this._onContextLost, false) | ||||
| if (options.dropzone) { | |||||
| this.addPluginSync(new DropzonePlugin(typeof options.dropzone === 'object' ? options.dropzone : undefined)) | |||||
| } | |||||
| this.console.log('ThreePipe Viewer instance initialized, version: ', ThreeViewer.VERSION) | this.console.log('ThreePipe Viewer instance initialized, version: ', ThreeViewer.VERSION) | ||||
| } | } | ||||
| private _objectProcessor: IObjectProcessor = { | private _objectProcessor: IObjectProcessor = { | ||||
| /** | /** | ||||
| * Set the environment map of the scene from url or an {@link IAsset} object. | * Set the environment map of the scene from url or an {@link IAsset} object. | ||||
| * @param map | * @param map | ||||
| * @param options | |||||
| * @param setBackground - Set the background image of the scene from the same map. | |||||
| * @param options - Options for importing the asset. See {@link ImportAssetOptions} | |||||
| */ | */ | ||||
| async setEnvironmentMap(map: string | IAsset | null, options?: ImportAssetOptions): Promise<void> { | |||||
| this._scene.environment = map ? await this.assetManager.importer.importSingle<ITexture>(map, options) || null : null | |||||
| async setEnvironmentMap(map: string | IAsset | null | ITexture, {setBackground = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<void> { | |||||
| this._scene.environment = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null | |||||
| if (setBackground) return this.setBackgroundMap(this._scene.environment) | |||||
| } | } | ||||
| /** | /** | ||||
| * Set the background image of the scene from url or an {@link IAsset} object. | * Set the background image of the scene from url or an {@link IAsset} object. | ||||
| * @param map | * @param map | ||||
| * @param options | |||||
| * @param setEnvironment - Set the environment map of the scene from the same map. | |||||
| * @param options - Options for importing the asset. See {@link ImportAssetOptions} | |||||
| */ | */ | ||||
| async setBackgroundMap(map: string | IAsset | null, options?: ImportAssetOptions): Promise<void> { | |||||
| this._scene.background = map ? await this.assetManager.importer.importSingle<ITexture>(map, options) || null : null | |||||
| async setBackgroundMap(map: string | IAsset | null | ITexture, {setEnvironment = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<void> { | |||||
| this._scene.background = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null | |||||
| if (setEnvironment) return this.setEnvironmentMap(this._scene.background) | |||||
| } | } | ||||
| /** | /** | ||||
| * Deserialize and import all the viewer and plugin settings, exported with {@link exportConfig}. | * Deserialize and import all the viewer and plugin settings, exported with {@link exportConfig}. | ||||
| */ | */ | ||||
| async importConfig(json: ISerializedConfig|ISerializedViewerConfig) { | async importConfig(json: ISerializedConfig|ISerializedViewerConfig) { | ||||
| if (json.type !== this.type || <string>json.type !== 'ViewerApp') { | |||||
| if (json.type !== this.type && <string>json.type !== 'ViewerApp') { | |||||
| if (this.getPlugin(json.type)) { | if (this.getPlugin(json.type)) { | ||||
| return this.importPluginConfig(json) | return this.importPluginConfig(json) | ||||
| } else { | } else { |