| ImportFilesOptions, | ImportFilesOptions, | ||||
| ImportResult, | ImportResult, | ||||
| LoadFileOptions, | LoadFileOptions, | ||||
| ProcessRawOptions, | |||||
| ProcessRawOptions, RootSceneImportResult, | |||||
| } from './IAssetImporter' | } from './IAssetImporter' | ||||
| import {IAsset, IFile} from './IAsset' | import {IAsset, IFile} from './IAsset' | ||||
| import {IImporter, ILoader} from './IImporter' | import {IImporter, ILoader} from './IImporter' | ||||
| if (result) result = await this.processRaw(result, options, path) | if (result) result = await this.processRaw(result, options, path) | ||||
| if (result) { | if (result) { | ||||
| if (options.processRaw !== false) asset.preImported = result | if (options.processRaw !== false) asset.preImported = result | ||||
| // asset.preImportedRaw = undefined | |||||
| // asset.preImported = undefined | |||||
| const arrs: any[] = [] | const arrs: any[] = [] | ||||
| if (Array.isArray(result)) arrs.push(...result) | |||||
| else { | |||||
| if (result.userData?.rootSceneModelRoot) arrs.push(...result.children) | |||||
| else arrs.push(result) | |||||
| const push = (r: typeof result)=>{ | |||||
| if (r.userData?.rootSceneModelRoot) arrs.push(...r.children) | |||||
| else arrs.push(r) | |||||
| } | } | ||||
| // remove preImportedRaw when any of the assets is disposed. This is to prevent memory leaks | |||||
| if (Array.isArray(result)) result.map(push) | |||||
| else push(result) | |||||
| // remove preImportedRaw when any one of the assets is disposed. todo maybe do when ALL are dispoed? | |||||
| arrs.forEach(r=>r.addEventListener?.('dispose', () => { // todo: recheck after dispose logic change | arrs.forEach(r=>r.addEventListener?.('dispose', () => { // todo: recheck after dispose logic change | ||||
| if (asset?.preImportedRaw) asset.preImportedRaw = undefined | if (asset?.preImportedRaw) asset.preImportedRaw = undefined | ||||
| if (asset?.preImported) asset.preImported = undefined | if (asset?.preImported) asset.preImported = undefined | ||||
| // it's a bit hacky to do this here, but it works for now. todo: move to a better place | // it's a bit hacky to do this here, but it works for now. todo: move to a better place | ||||
| let ress: any[] = [] | let ress: any[] = [] | ||||
| if (Array.isArray(res)) ress = res.flat(2) | if (Array.isArray(res)) ress = res.flat(2) | ||||
| else if ((<IObject3D>res)?.userData?.rootSceneModelRoot) ress.push(...(<IObject3D>res).children) | |||||
| else if ((<RootSceneImportResult>res)?.userData?.rootSceneModelRoot) ress.push(...(<IObject3D>res).children) | |||||
| else ress.push(res) | else ress.push(res) | ||||
| for (const r of ress) r?.addEventListener?.('dispose', () => file.__loadedAsset = undefined) | for (const r of ress) r?.addEventListener?.('dispose', () => file.__loadedAsset = undefined) | ||||
| if (Array.isArray(res)) { | if (Array.isArray(res)) { | ||||
| const r: any[] = [] | const r: any[] = [] | ||||
| for (const re of res) { // todo: can we parallelize? | |||||
| for (const re of res) { // todo: should we parallelize? | |||||
| r.push(...await this.processRaw(re, options, path)) | r.push(...await this.processRaw(re, options, path)) | ||||
| } | } | ||||
| return r | return r | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if ((res as RootSceneImportResult).userData.rootSceneModelRoot) { | |||||
| res._childrenCopy = [...res.children] | |||||
| } | |||||
| // if (res.assetType) // todo: why if? | // if (res.assetType) // todo: why if? | ||||
| res.assetImporterProcessed = true // this should not be put in userData | res.assetImporterProcessed = true // this should not be put in userData |
| gltfAsset?: GLTF['asset'] | gltfAsset?: GLTF['asset'] | ||||
| [key: string]: any | [key: string]: any | ||||
| } | } | ||||
| /** | |||||
| * copy of children to use after import, set in processRaw | |||||
| * @internal | |||||
| */ | |||||
| _childrenCopy?: Object3D[] | |||||
| } | } | ||||
| export type ImportResultObject = IObject3D | ITexture | ICamera | ISerializedConfig | ISerializedViewerConfig | RootSceneImportResult | IMaterial | export type ImportResultObject = IObject3D | ITexture | ICamera | ISerializedConfig | ISerializedViewerConfig | RootSceneImportResult | IMaterial |
| this.clearSceneModels(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('RootScene: Invalid model root scene object. Trying to add anyway.', obj) | |||||
| } | } | ||||
| if (obj.userData) { | if (obj.userData) { | ||||
| // todo deep merge all userdata? | // todo deep merge all userdata? | ||||
| this.modelRoot.animations.push(animation) | this.modelRoot.animations.push(animation) | ||||
| } | } | ||||
| } | } | ||||
| return [...obj.children] // need to clone | |||||
| .map(c=>this.addObject(c, {...options, clearSceneObjects: false, disposeSceneObjects: false})) | |||||
| const children = obj._childrenCopy || [...obj.children] | |||||
| return children.map(c=>this.addObject(c, {...options, clearSceneObjects: false, disposeSceneObjects: false})) | |||||
| } | } | ||||
| private _addObject3D(model: IObject3D|null, {autoCenter = false, centerGeometries = false, centerGeometriesKeepPosition = true, autoScale = false, autoScaleRadius = 2., addToRoot = false, license}: AddObjectOptions = {}): void { | private _addObject3D(model: IObject3D|null, {autoCenter = false, centerGeometries = false, centerGeometriesKeepPosition = true, autoScale = false, autoScaleRadius = 2., addToRoot = false, license}: AddObjectOptions = {}): void { | ||||
| disposeSceneModels(setDirty = true, clear = true) { | disposeSceneModels(setDirty = true, clear = true) { | ||||
| if (clear) { | if (clear) { | ||||
| [...this.modelRoot.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent()) | |||||
| for (const child of [...this.modelRoot.children]) { | |||||
| child.dispose ? child.dispose() : child.removeFromParent() | |||||
| } | |||||
| this.modelRoot.clear() | this.modelRoot.clear() | ||||
| if (setDirty) this.setDirty({refreshScene: true}) | if (setDirty) this.setDirty({refreshScene: true}) | ||||
| } else { | } else { | ||||
| this.modelRoot.children.forEach(child => child.dispose && child.dispose()) | |||||
| for (const child of this.modelRoot.children) { | |||||
| child.dispose && child.dispose(false) | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| Vector2, | Vector2, | ||||
| Vector3, | Vector3, | ||||
| } from 'three' | } from 'three' | ||||
| import {Class, createCanvasElement, downloadBlob, onChange, serialize, timeout, ValOrArr} from 'ts-browser-helpers' | |||||
| import {Class, createCanvasElement, downloadBlob, onChange, serialize, ValOrArr} from 'ts-browser-helpers' | |||||
| import {TViewerScreenShader} from '../postprocessing' | import {TViewerScreenShader} from '../postprocessing' | ||||
| import { | import { | ||||
| AddObjectOptions, | AddObjectOptions, | ||||
| } | } | ||||
| deleteImportedViewerConfigOnLoad = true | deleteImportedViewerConfigOnLoad = true | ||||
| deleteImportedViewerConfigOnLoadWait = 2000 // ms | |||||
| /** | /** | ||||
| * Add an object to the scene model root. | * Add an object to the scene model root. | ||||
| if (imported.userData?.rootSceneModelRoot) { | if (imported.userData?.rootSceneModelRoot) { | ||||
| const obj = <RootSceneImportResult>imported | const obj = <RootSceneImportResult>imported | ||||
| this._scene.loadModelRoot(obj, options) | this._scene.loadModelRoot(obj, options) | ||||
| if (obj.importedViewerConfig && options?.importConfig !== false) await this.importConfig(obj.importedViewerConfig) | |||||
| if (options?.importConfig !== false) { | |||||
| if (obj.importedViewerConfig) { | |||||
| await this.importConfig(obj.importedViewerConfig) | |||||
| // @ts-expect-error no type for this | |||||
| if (obj._deletedImportedViewerConfig) delete obj._deletedImportedViewerConfig | |||||
| // @ts-expect-error no type for this | |||||
| } else if (obj._deletedImportedViewerConfig) | |||||
| this.console.error('ThreeViewer - Imported viewer config was deleted, cannot import it again. Set `viewer.deleteImportedViewerConfigOnLoad` to `false` to keep it in the object for reuse workflows.') | |||||
| } | |||||
| if (this.deleteImportedViewerConfigOnLoad && obj.importedViewerConfig) { | if (this.deleteImportedViewerConfigOnLoad && obj.importedViewerConfig) { | ||||
| timeout(2000).then(()=>{ // todo timeout time? | |||||
| setTimeout(()=>{ | |||||
| if (!obj.importedViewerConfig) return | |||||
| delete obj.importedViewerConfig // any useful data in the config should be loaded into userData.__importData by then | delete obj.importedViewerConfig // any useful data in the config should be loaded into userData.__importData by then | ||||
| }) | |||||
| // @ts-expect-error no type for this | |||||
| obj._deletedImportedViewerConfig = true // for console warning above | |||||
| }, this.deleteImportedViewerConfigOnLoadWait) | |||||
| } | } | ||||
| return this._scene.modelRoot as T | return this._scene.modelRoot as T | ||||
| } | } |