| @@ -6,7 +6,7 @@ import { | |||
| ImportFilesOptions, | |||
| ImportResult, | |||
| LoadFileOptions, | |||
| ProcessRawOptions, | |||
| ProcessRawOptions, RootSceneImportResult, | |||
| } from './IAssetImporter' | |||
| import {IAsset, IFile} from './IAsset' | |||
| import {IImporter, ILoader} from './IImporter' | |||
| @@ -199,14 +199,16 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| if (result) result = await this.processRaw(result, options, path) | |||
| if (result) { | |||
| if (options.processRaw !== false) asset.preImported = result | |||
| // asset.preImportedRaw = undefined | |||
| // asset.preImported = undefined | |||
| 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 | |||
| if (asset?.preImportedRaw) asset.preImportedRaw = undefined | |||
| if (asset?.preImported) asset.preImported = undefined | |||
| @@ -351,7 +353,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| // it's a bit hacky to do this here, but it works for now. todo: move to a better place | |||
| let ress: any[] = [] | |||
| 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) | |||
| for (const r of ress) r?.addEventListener?.('dispose', () => file.__loadedAsset = undefined) | |||
| @@ -425,7 +427,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| if (Array.isArray(res)) { | |||
| 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)) | |||
| } | |||
| return r | |||
| @@ -457,6 +459,9 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| } | |||
| } | |||
| } | |||
| if ((res as RootSceneImportResult).userData.rootSceneModelRoot) { | |||
| res._childrenCopy = [...res.children] | |||
| } | |||
| // if (res.assetType) // todo: why if? | |||
| res.assetImporterProcessed = true // this should not be put in userData | |||
| @@ -16,6 +16,12 @@ export interface RootSceneImportResult extends Object3D { | |||
| gltfAsset?: GLTF['asset'] | |||
| [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 | |||
| @@ -224,7 +224,7 @@ export class RootScene<TE extends ISceneEventMap = ISceneEventMap> extends Scene | |||
| this.clearSceneModels(options.disposeSceneObjects) | |||
| } | |||
| 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) { | |||
| // todo deep merge all userdata? | |||
| @@ -253,8 +253,8 @@ export class RootScene<TE extends ISceneEventMap = ISceneEventMap> extends Scene | |||
| 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 { | |||
| @@ -311,11 +311,15 @@ export class RootScene<TE extends ISceneEventMap = ISceneEventMap> extends Scene | |||
| disposeSceneModels(setDirty = true, clear = true) { | |||
| 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() | |||
| if (setDirty) this.setDirty({refreshScene: true}) | |||
| } else { | |||
| this.modelRoot.children.forEach(child => child.dispose && child.dispose()) | |||
| for (const child of this.modelRoot.children) { | |||
| child.dispose && child.dispose(false) | |||
| } | |||
| } | |||
| } | |||
| @@ -12,7 +12,7 @@ import { | |||
| Vector2, | |||
| Vector3, | |||
| } 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 { | |||
| AddObjectOptions, | |||
| @@ -1003,6 +1003,7 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| } | |||
| deleteImportedViewerConfigOnLoad = true | |||
| deleteImportedViewerConfigOnLoadWait = 2000 // ms | |||
| /** | |||
| * Add an object to the scene model root. | |||
| @@ -1014,12 +1015,23 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| if (imported.userData?.rootSceneModelRoot) { | |||
| const obj = <RootSceneImportResult>imported | |||
| 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) { | |||
| 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 | |||
| }) | |||
| // @ts-expect-error no type for this | |||
| obj._deletedImportedViewerConfig = true // for console warning above | |||
| }, this.deleteImportedViewerConfigOnLoadWait) | |||
| } | |||
| return this._scene.modelRoot as T | |||
| } | |||