| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Asset Exporter Plugin</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- 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", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </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> | |||||
| </body> |
| import { | |||||
| _testFinish, | |||||
| AssetExporterPlugin, | |||||
| IObject3D, | |||||
| LoadingScreenPlugin, | |||||
| SceneUiConfigPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| plugins: [LoadingScreenPlugin, AssetExporterPlugin, SceneUiConfigPlugin], | |||||
| }) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||||
| setBackground: true, | |||||
| }) | |||||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| ui.setupPluginUi(AssetExporterPlugin, {expanded: true}) | |||||
| ui.setupPluginUi(SceneUiConfigPlugin) | |||||
| const model = result?.getObjectByName('node_damagedHelmet_-6514') | |||||
| const config = model?.uiConfig | |||||
| if (config) ui.appendChild(config) | |||||
| } | |||||
| init().finally(_testFinish) |
| async function init() { | async function init() { | ||||
| // Note: see asset-exporter-plugin example as well | |||||
| // load obj + mtl | // load obj + mtl | ||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | ||||
| const helmet = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | const helmet = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { |
| autoSetBackground: true, | autoSetBackground: true, | ||||
| }, | }, | ||||
| }, | }, | ||||
| // add optional plugins to load gltf with extensions like meshopt_compression, ktx2 etc | |||||
| // plugins: [GLTFMeshOptDecodePlugin, KTX2LoadPlugin, GLTFKHRMaterialVariantsPlugin, ...], | |||||
| }) | }) | ||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>GLTF MeshOpt Decode Plugin (EXT_meshopt_compression)</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- 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; | |||||
| } | |||||
| </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> | |||||
| </body> |
| import { | |||||
| _testFinish, | |||||
| GLTFMeshOptDecodePlugin, | |||||
| IObject3D, | |||||
| KTX2LoadPlugin, | |||||
| LoadingScreenPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| dropzone: { | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| }, | |||||
| }, | |||||
| plugins: [LoadingScreenPlugin, GLTFMeshOptDecodePlugin, KTX2LoadPlugin], | |||||
| }) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const options = { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }; | |||||
| (await Promise.all([ | |||||
| viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/coffeemat.glb', options), | |||||
| viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/facecap.glb', options), | |||||
| ])).forEach((result, i) => { | |||||
| if (!result) return | |||||
| result.position.x = i * 2 - 1 | |||||
| }) | |||||
| } | |||||
| init().finally(_testFinish) |
| } | } | ||||
| const needsMeshOpt = parser.json?.extensionsUsed?.includes?.('EXT_meshopt_compression') | const needsMeshOpt = parser.json?.extensionsUsed?.includes?.('EXT_meshopt_compression') | ||||
| if (needsMeshOpt) { | if (needsMeshOpt) { | ||||
| if ((window as any).MeshoptDecoder) { // added by the plugin or by the user | |||||
| this.setMeshoptDecoder((window as any).MeshoptDecoder) | |||||
| parser.options.meshoptDecoder = (window as any).MeshoptDecoder as any | |||||
| if (window.MeshoptDecoder) { // added by the plugin or by the user | |||||
| this.setMeshoptDecoder(window.MeshoptDecoder) | |||||
| parser.options.meshoptDecoder = window.MeshoptDecoder | |||||
| } else { | } else { | ||||
| console.error('Add GLTFMeshOptPlugin to viewer to enable EXT_meshopt_compression decode') | |||||
| console.error('Add GLTFMeshOptPlugin(and initialize it) to viewer to enable EXT_meshopt_compression decode') | |||||
| } | } | ||||
| } | } | ||||
| const needsBasisU = parser.json?.extensionsUsed?.includes?.('KHR_texture_basisu') | const needsBasisU = parser.json?.extensionsUsed?.includes?.('KHR_texture_basisu') |
| import {serialize} from 'ts-browser-helpers' | |||||
| import {BlobExt, ExportFileOptions} from '../../assetmanager' | |||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||||
| import {UiObjectConfig} from 'uiconfig.js' | |||||
| import {PickingPlugin} from '../interaction/PickingPlugin' | |||||
| // export enum EncoderMethod { | |||||
| // EDGEBREAKER = 1, | |||||
| // SEQUENTIAL = 0 | |||||
| // } | |||||
| export interface ExportAssetOptions extends ExportFileOptions { | |||||
| convertMeshToIndexed?: boolean // convert mesh to indexed geometry | |||||
| name?: string | |||||
| compress?: boolean, | |||||
| } | |||||
| // todo deprecate the plugin and add functionality to AssetManager maybe | |||||
| /** | |||||
| * Asset Exporter Plugin | |||||
| * Provides options and methods to export the scene, object GLB or Viewer Config. | |||||
| * All the functionality is available in the viewer directly, this provides only a ui-config and maintains state of the options. | |||||
| */ | |||||
| export class AssetExporterPlugin extends AViewerPluginSync<''> { | |||||
| public static readonly PluginType = 'AssetExporterPlugin' | |||||
| enabled = true | |||||
| constructor() { | |||||
| super() | |||||
| this.exportScene = this.exportScene.bind(this) | |||||
| } | |||||
| onAdded(viewer: ThreeViewer) { | |||||
| super.onAdded(viewer) | |||||
| // todo Convert all non-indexed geometries to indexed geometries, because DRACO compression requires indexed geometry. | |||||
| // this.exporter.processors.add('model', { | |||||
| // forAssetType: 'model', | |||||
| // processAsync: async(obj: IObject3D, options) => { | |||||
| // if (options.convertMeshToIndexed) | |||||
| // obj?.traverse((o: IObject3D)=>{ | |||||
| // if (!o.geometry) return | |||||
| // if (o.geometry.attributes.index) return | |||||
| // o.geometry = toIndexedGeometry(o.geometry) | |||||
| // }) | |||||
| // return obj | |||||
| // }, | |||||
| // }) | |||||
| } | |||||
| onRemove(viewer: ThreeViewer) { | |||||
| return super.onRemove(viewer) | |||||
| } | |||||
| @serialize() exportOptions: ExportAssetOptions = { | |||||
| name: 'scene', | |||||
| viewerConfig: true, | |||||
| encodeUint16Rgbe: false, | |||||
| convertMeshToIndexed: false, | |||||
| embedUrlImagePreviews: false, | |||||
| embedUrlImages: false, | |||||
| // compress: false, | |||||
| // dracoOptions: { | |||||
| // encodeSpeed: 5, | |||||
| // method: EncoderMethod.EDGEBREAKER, | |||||
| // quantizationVolume: 'mesh', | |||||
| // quantizationBits: { | |||||
| // ['POSITION']: 14, | |||||
| // ['NORMAL']: 10, | |||||
| // ['COLOR']: 8, | |||||
| // ['TEX_COORD']: 12, | |||||
| // ['GENERIC']: 12, | |||||
| // }, | |||||
| // } as EncoderOptions, | |||||
| encrypt: false, | |||||
| encryptKey: '', | |||||
| } | |||||
| async exportScene(options?: ExportAssetOptions): Promise<BlobExt | undefined> { | |||||
| return this._viewer?.assetManager.exporter?.exportObject(this._viewer?.scene.modelRoot, options || {...this.exportOptions}) | |||||
| } | |||||
| async downloadSceneGlb() { | |||||
| const blob = await this.exportScene(this.exportOptions) | |||||
| if (blob) await this._viewer?.exportBlob(blob, this.exportOptions.name + '.' + blob.ext) | |||||
| } | |||||
| async exportSelected(options?: ExportAssetOptions, download = true) { | |||||
| const selected = this._viewer?.getPlugin<PickingPlugin>('PickingPlugin')?.getSelectedObject() as any | |||||
| if (!selected) { | |||||
| alert('Nothing selected') | |||||
| return | |||||
| } | |||||
| const blob = await this._viewer!.assetManager.exporter.exportObject(selected, options ?? this.exportOptions) | |||||
| if (blob && download) await this._viewer?.exportBlob(blob, 'object.' + blob.ext) | |||||
| return blob | |||||
| } | |||||
| uiConfig: UiObjectConfig = { | |||||
| type: 'folder', | |||||
| label: 'Asset Export', | |||||
| children: [ | |||||
| { | |||||
| type: 'input', | |||||
| property: [this.exportOptions, 'name'], | |||||
| }, | |||||
| { | |||||
| type: 'folder', | |||||
| label: 'GLB Export', | |||||
| children: [ | |||||
| // compress ? { | |||||
| // type: 'checkbox', | |||||
| // label: 'DRACO Compress', | |||||
| // property: [this.exportOptions, 'compress'], | |||||
| // onChange: ()=>this.uiConfig.uiRefresh?.(true), | |||||
| // } : {}, | |||||
| // compress && this.exportOptions.dracoOptions ? { | |||||
| // type: 'folder', | |||||
| // hidden: ()=>!this.exportOptions.compress, | |||||
| // label: 'DRACO Options', | |||||
| // children: [ | |||||
| // { | |||||
| // type: 'slider', | |||||
| // label: 'Encode Speed', | |||||
| // bounds: [1, 10], | |||||
| // property: [this.exportOptions.dracoOptions, 'encodeSpeed'], | |||||
| // }, | |||||
| // { | |||||
| // type: 'dropdown', | |||||
| // label: 'Encoder Method', | |||||
| // property: [this.exportOptions.dracoOptions, 'method'], | |||||
| // children: Object.entries(EncoderMethod).map(([k, v]) => ({label: k, value: v})), | |||||
| // }, | |||||
| // { | |||||
| // type: 'dropdown', | |||||
| // label: 'Quantization Volume', | |||||
| // property: [this.exportOptions.dracoOptions, 'quantizationVolume'], | |||||
| // children: ['mesh', 'scene', 'bbox'].map(v => ({label: v})), | |||||
| // }, | |||||
| // { | |||||
| // type: 'folder', | |||||
| // label: 'Quantization Bits', | |||||
| // children: Object.keys(this.exportOptions.dracoOptions.quantizationBits!).map(k => ({ | |||||
| // type: 'slider', | |||||
| // label: k, | |||||
| // bounds: [1, 16], | |||||
| // stepSize: 1, | |||||
| // property: [this.exportOptions.dracoOptions.quantizationBits, k], | |||||
| // })), | |||||
| // }, | |||||
| // ], | |||||
| // } : {}, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Viewer Config (All Settings)', | |||||
| property: [this.exportOptions, 'viewerConfig'], | |||||
| onChange: ()=>this.uiConfig.uiRefresh?.(true), | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Embed Image Previews', | |||||
| property: [this.exportOptions, 'embedUrlImagePreviews'], | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Encrypt', | |||||
| property: [this.exportOptions, 'encrypt'], | |||||
| onChange: ()=>this.uiConfig.uiRefresh?.(true), | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Encrypt Password', | |||||
| hidden: ()=>!this.exportOptions.encrypt, | |||||
| property: [this.exportOptions, 'encryptKey'], | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Compress hdr env maps', | |||||
| hidden: ()=>!this.exportOptions.viewerConfig, | |||||
| property: [this.exportOptions, 'encodeUint16Rgbe'], | |||||
| }, | |||||
| // { // todo | |||||
| // type: 'checkbox', | |||||
| // label: 'Convert to indexed', | |||||
| // property: [this.exportOptions, 'convertMeshToIndexed'], | |||||
| // }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Export GLB', | |||||
| property: [this, 'downloadSceneGlb'], | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Export Viewer Config', | |||||
| value: async()=>{ | |||||
| const blob = new Blob([JSON.stringify(this._viewer?.exportConfig(false), null, 2)], {type: 'application/json'}) | |||||
| if (blob) await this._viewer?.exportBlob(blob, this.exportOptions.name + '.' + ThreeViewer.ConfigTypeSlug) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Export Selected', | |||||
| hidden: ()=>!this._viewer?.getPlugin<PickingPlugin>('PickingPlugin'), | |||||
| value: async()=>this.exportSelected(this.exportOptions, true), | |||||
| }, | |||||
| ], | |||||
| } | |||||
| } |
| import {IViewerPluginSync} from '../../viewer' | |||||
| import {SimpleEventDispatcher} from 'ts-browser-helpers' | |||||
| /** | |||||
| * Loads the MeshOpt Decoder module from [meshoptimizer](https://github.com/zeux/meshoptimizer) library at runtime from a customisable cdn url. | |||||
| * The loaded module is set in window.MeshoptDecoder and then used by {@link GLTFLoader2} to decode files using [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) extension | |||||
| * | |||||
| * The plugin name includes GLTF, but its not really GLTF specific, it can be used to decode any meshopt compressed files. | |||||
| */ | |||||
| export class GLTFMeshOptDecodePlugin extends SimpleEventDispatcher<''> implements IViewerPluginSync { | |||||
| declare ['constructor']: typeof GLTFMeshOptDecodePlugin | |||||
| public static readonly PluginType = 'GLTFMeshOptDecodePlugin' | |||||
| enabled = true | |||||
| toJSON: any = undefined | |||||
| constructor(initialize = true, public readonly rootNode = document.head) { | |||||
| super() | |||||
| // todo: check if compatible? | |||||
| if (initialize) this.initialize() | |||||
| } | |||||
| get initialized() { | |||||
| return !!window.MeshoptDecoder | |||||
| } | |||||
| // static DECODER_URL = 'https://cdn.jsdelivr.net/gh/zeux/meshoptimizer@master/js/meshopt_decoder.module.js' | |||||
| static DECODER_URL = 'https://unpkg.com/meshoptimizer@0.20.0/meshopt_decoder.module.js' | |||||
| protected _script?: HTMLScriptElement | |||||
| protected _initializing?: Promise<void> = undefined | |||||
| async initialize() { | |||||
| if (this.initialized) return | |||||
| if (this._initializing) return await this._initializing | |||||
| const s = document.createElement('script') | |||||
| s.type = 'module' | |||||
| const ev = Math.random().toString(36).substring(7) | |||||
| s.innerHTML = ` | |||||
| import { MeshoptDecoder } from '${GLTFMeshOptDecodePlugin.DECODER_URL}'; | |||||
| window.MeshoptDecoder = MeshoptDecoder; // setting it before ready as GLTFLoader supports it. | |||||
| MeshoptDecoder.ready.then(() => { | |||||
| window.dispatchEvent(new CustomEvent('${ev}')) | |||||
| }); | |||||
| ` | |||||
| this._initializing = new Promise<void>((res) => { | |||||
| window.addEventListener(ev, ()=>res(), {once: true}) | |||||
| this.rootNode.appendChild(s) | |||||
| this._script = s | |||||
| }) | |||||
| return await this._initializing | |||||
| } | |||||
| dispose() { | |||||
| if (this._script) { | |||||
| this._script.remove() | |||||
| delete window.MeshoptDecoder | |||||
| } | |||||
| this._script = undefined | |||||
| } | |||||
| onAdded(): void { return } | |||||
| onRemove(): void { return } | |||||
| } | |||||
| declare global{ | |||||
| interface Window{ | |||||
| MeshoptDecoder?: any | |||||
| } | |||||
| } |
| export {STLLoadPlugin} from './import/STLLoadPlugin' | export {STLLoadPlugin} from './import/STLLoadPlugin' | ||||
| export {KTXLoadPlugin} from './import/KTXLoadPlugin' | export {KTXLoadPlugin} from './import/KTXLoadPlugin' | ||||
| export {KTX2LoadPlugin} from './import/KTX2LoadPlugin' | export {KTX2LoadPlugin} from './import/KTX2LoadPlugin' | ||||
| export {GLTFMeshOptDecodePlugin} from './import/GLTFMeshOptDecodePlugin' | |||||
| // export | // export | ||||
| export {AssetExporterPlugin} from './export/AssetExporterPlugin' | |||||
| export {CanvasSnapshotPlugin, CanvasSnipperPlugin} from './export/CanvasSnapshotPlugin' | export {CanvasSnapshotPlugin, CanvasSnipperPlugin} from './export/CanvasSnapshotPlugin' | ||||
| export {FileTransferPlugin} from './export/FileTransferPlugin' | export {FileTransferPlugin} from './export/FileTransferPlugin' | ||||
| import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | ||||
| import {uiConfig, UiObjectConfig, uiPanelContainer} from 'uiconfig.js' | import {uiConfig, UiObjectConfig, uiPanelContainer} from 'uiconfig.js' | ||||
| import {IRenderTarget} from '../rendering' | import {IRenderTarget} from '../rendering' | ||||
| import type {CanvasSnapshotPlugin, FileTransferPlugin} from '../plugins' | |||||
| import {CameraViewPlugin, ProgressivePlugin} from '../plugins' | |||||
| import { | |||||
| AssetExporterPlugin, | |||||
| CameraViewPlugin, | |||||
| CanvasSnapshotPlugin, | |||||
| FileTransferPlugin, | |||||
| ProgressivePlugin, | |||||
| } from '../plugins' | |||||
| // noinspection ES6PreferShortImport | // noinspection ES6PreferShortImport | ||||
| import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | ||||
| // noinspection ES6PreferShortImport | // noinspection ES6PreferShortImport | ||||
| * @param options | * @param options | ||||
| */ | */ | ||||
| async export(obj?: IObject3D|IMaterial|ITexture|IRenderTarget|IViewerPlugin|(typeof this), options?: ExportFileOptions) { | async export(obj?: IObject3D|IMaterial|ITexture|IRenderTarget|IViewerPlugin|(typeof this), options?: ExportFileOptions) { | ||||
| if (!obj) obj = this._scene // this will export the glb with the scene and viewer config | |||||
| if (!obj) obj = this._scene.modelRoot // this will export the glb with the scene and viewer config | |||||
| if ((<typeof this>obj).type === this.type) return jsonToBlob((<typeof this>obj).exportConfig()) | if ((<typeof this>obj).type === this.type) return jsonToBlob((<typeof this>obj).exportConfig()) | ||||
| if ((<IViewerPlugin>obj).constructor?.PluginType) return jsonToBlob(this.exportPluginConfig(<IViewerPlugin>obj)) | if ((<IViewerPlugin>obj).constructor?.PluginType) return jsonToBlob(this.exportPluginConfig(<IViewerPlugin>obj)) | ||||
| return await this.assetManager.exporter.exportObject(<IObject3D|IMaterial|ITexture|IRenderTarget>obj, options) | return await this.assetManager.exporter.exportObject(<IObject3D|IMaterial|ITexture|IRenderTarget>obj, options) | ||||
| /** | /** | ||||
| * Export the scene to a file (default: glb with viewer config) and return a blob | * Export the scene to a file (default: glb with viewer config) and return a blob | ||||
| * @param options | * @param options | ||||
| * @param useExporterPlugin - uses the {@link AssetExporterPlugin} if available. This is useful to use the options configured by the user in the plugin. | |||||
| */ | */ | ||||
| async exportScene(options?: ExportFileOptions): Promise<BlobExt | undefined> { | |||||
| async exportScene(options?: ExportFileOptions, useExporterPlugin = true): Promise<BlobExt | undefined> { | |||||
| const exporter = useExporterPlugin ? this.getPlugin<AssetExporterPlugin>('AssetExporterPlugin') : undefined | |||||
| if (exporter) return exporter.exportScene(options) | |||||
| return this.assetManager.exporter.exportObject(this._scene.modelRoot, options) | return this.assetManager.exporter.exportObject(this._scene.modelRoot, options) | ||||
| } | } | ||||
| /** | |||||
| * Returns a blob with the screenshot of the canvas. | |||||
| * If {@link CanvasSnapshotPlugin} is added, it will be used, otherwise canvas.toBlob will be used directly. | |||||
| * @param mimeType default image/jpeg | |||||
| * @param quality between 0 and 100 | |||||
| */ | |||||
| async getScreenshotBlob({mimeType = 'image/jpeg', quality = 90} = {}): Promise<Blob | null | undefined> { | async getScreenshotBlob({mimeType = 'image/jpeg', quality = 90} = {}): Promise<Blob | null | undefined> { | ||||
| const plugin = this.getPlugin<CanvasSnapshotPlugin>('CanvasSnapshotPlugin') | const plugin = this.getPlugin<CanvasSnapshotPlugin>('CanvasSnapshotPlugin') | ||||
| if (plugin) { | if (plugin) { | ||||
| /** | /** | ||||
| * Serialize all the viewer and plugin settings and versions. | * Serialize all the viewer and plugin settings and versions. | ||||
| * @param binary - Indicate that the output will be converted and saved as binary data. (default: true) | * @param binary - Indicate that the output will be converted and saved as binary data. (default: true) | ||||
| * @param pluginFilter - List of PluginType to include. If empty, no plugins will be serialized. If undefined, all plugins will be serialized. | |||||
| * @param pluginFilter - List of PluginType to include. If empty, no plugins will be serialized. If undefined/not-passed, all plugins will be serialized. | |||||
| * @returns {any} - Serializable JSON object. | * @returns {any} - Serializable JSON object. | ||||
| */ | */ | ||||
| toJSON(binary = true, pluginFilter?: string[]): ISerializedViewerConfig { | toJSON(binary = true, pluginFilter?: string[]): ISerializedViewerConfig { | ||||
| /** | /** | ||||
| * Uses the {@link FileTransferPlugin} to export a Blob/File. If the plugin is not available, it will download the blob. | * Uses the {@link FileTransferPlugin} to export a Blob/File. If the plugin is not available, it will download the blob. | ||||
| * `FileTransferPlugin` can be configured by other plugins to export the blob to a specific location like local file system, cloud storage, etc. | |||||
| * {@link FileTransferPlugin} can be configured by other plugins to export the blob to a specific location like local file system, cloud storage, etc. | |||||
| * @param blob - The blob or file to export/download | * @param blob - The blob or file to export/download | ||||
| * @param name - name of the file, if not provided, the name of the file is used if it's a file. | * @param name - name of the file, if not provided, the name of the file is used if it's a file. | ||||
| */ | */ |