threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
748 lines (674 loc) • 30.8 kB
text/typescript
import {ImportAssetOptions, ImportResult, ProcessRawOptions, RootSceneImportResult} from './IAssetImporter'
import {
BaseEvent,
Camera,
EventDispatcher,
Light,
LinearFilter,
LinearMipmapLinearFilter,
LoadingManager,
Object3D,
TextureLoader,
} from 'three'
import {ISerializedConfig, IViewerPlugin, ThreeViewer} from '../viewer'
import {AssetImporter} from './AssetImporter'
import {getTextureDataType} from '../three'
import {IAsset} from './IAsset'
import {
AddObjectOptions,
AmbientLight2,
DirectionalLight2,
HemisphereLight2,
ICamera,
iCameraCommons,
ILight,
iLightCommons,
IMaterial,
iMaterialCommons,
IObject3D,
iObjectCommons,
ISceneEventMap,
ITexture,
OrthographicCamera2,
PerspectiveCamera2,
PointLight2,
RectAreaLight2,
SpotLight2,
upgradeTexture,
} from '../core'
import {Importer} from './Importer'
import {MaterialManager} from './MaterialManager'
import {
DRACOLoader2,
FBXLoader2,
GLTFLoader2,
JSONMaterialLoader,
MTLLoader2,
OBJLoader2,
SVGTextureLoader,
VideoTextureLoader,
ZipLoader,
} from './import'
import {RGBELoader} from 'three/examples/jsm/loaders/RGBELoader.js'
import {EXRLoader} from 'three/examples/jsm/loaders/EXRLoader.js'
import {Class, ValOrArr} from 'ts-browser-helpers'
import {ILoader} from './IImporter'
import {AssetExporter} from './AssetExporter'
import {IExporter} from './IExporter'
import {GLTFExporter2, GLTFWriter2} from './export'
import {legacySeparateMapSamplerUVFix} from '../utils/legacy'
import type {GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader.js'
import {GLTFExporterPlugin} from 'three/examples/jsm/exporters/GLTFExporter.js'
// todo rename to AssetImporterCacheOptions
export interface AssetManagerOptions{
/**
* simple memory based cache for downloaded files, default = false
*/
simpleCache?: boolean
/**
* Cache Storage for downloaded files, can use with `caches.open`
* When true and by default uses `caches.open('threepipe-assetmanager')`, set to false to disable
* @default true
*/
storage?: Cache | Storage | boolean
}
export interface AddAssetOptions extends AddObjectOptions{
/**
* Automatically set any loaded HDR, EXR file as the scene environment map
* @default true
*/
autoSetEnvironment?: boolean
/**
* Automatically set any loaded image(ITexture) file as the scene background
*/
autoSetBackground?: boolean
}
export interface ImportAddOptions extends ImportAssetOptions, AddAssetOptions{}
export interface AddRawOptions extends ProcessRawOptions, AddAssetOptions{}
export interface AssetManagerEventMap{
loadAsset: {data: ImportResult}
processStateUpdate: object
}
/**
* Asset Manager
*
* Utility class to manage import, export, and material management.
* @category Asset Manager
*/
export class AssetManager extends EventDispatcher<AssetManagerEventMap> {
readonly viewer: ThreeViewer
readonly importer: AssetImporter
readonly exporter: AssetExporter
readonly materials: MaterialManager
get storage() {
return this.importer.storage
}
constructor(viewer: ThreeViewer, cacheOptions?: AssetManagerOptions) {
super()
this._sceneUpdated = this._sceneUpdated.bind(this)
this.addAsset = this.addAsset.bind(this)
this.addRaw = this.addRaw.bind(this)
this._loaderCreate = this._loaderCreate.bind(this)
this.addImported = this.addImported.bind(this)
this.importer = new AssetImporter(!!viewer.getPlugin('debug'), cacheOptions)
this.exporter = new AssetExporter()
this.materials = new MaterialManager()
this.viewer = viewer
this.viewer.scene.addEventListener('addSceneObject', this._sceneUpdated)
this.viewer.scene.addEventListener('materialChanged', this._sceneUpdated)
this.viewer.scene.addEventListener('beforeDeserialize', this._sceneUpdated)
this._setupGltfExtensions()
this._setupObjectProcess()
this._setupProcessState()
this._addImporters()
this._addExporters()
}
async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAddOptions): Promise<(T | undefined)[]> {
if (!this.importer || !this.viewer) return []
const imported = await this.importer.import<T>(assetOrPath, options)
if (!imported) {
const path = typeof assetOrPath === 'string' ? assetOrPath : (assetOrPath as IAsset)?.path
if (path && !path.split('?')[0].endsWith('.vjson'))
console.warn('Threepipe AssetManager - Unable to import', assetOrPath, imported)
return []
}
return this.loadImported<(T | undefined)[]>(imported, options)
}
// materials: IMaterial[] = []
// textures: ITexture[] = []
// todo move this function to viewer
async loadImported<T extends ValOrArr<ImportResult | undefined> = ImportResult>(imported: T, {
autoSetEnvironment = true,
autoSetBackground = false,
...options
}: AddAssetOptions = {}): Promise<T | never[]> {
const arr: (ImportResult | undefined)[] = Array.isArray(imported) ? imported : [imported]
let ret: T = Array.isArray(imported) ? [] : undefined as any
if (options?.importConfig !== false) {
const config = arr.find(v => v?.assetType === 'config') || arr.find(v=>v && !!v.importedViewerConfig)?.importedViewerConfig
if (config) legacySeparateMapSamplerUVFix(config, arr.filter(a=>a?.isObject3D) as Object3D[])
}
for (const obj of arr) {
if (!obj) {
if (Array.isArray(ret)) ret.push(undefined)
continue
}
let r = obj
switch (obj.assetType) {
case 'material':
this.materials.registerMaterial(<IMaterial>obj)
break
case 'texture':
if (autoSetEnvironment && (
obj.__rootPath?.endsWith('.hdr') || obj.__rootPath?.endsWith('.exr')
)) this.viewer.scene.environment = <ITexture>obj
if (autoSetBackground) this.viewer.scene.background = <ITexture>obj
break
case 'model':
case 'light':
case 'camera':
r = await this.viewer.addSceneObject(<IObject3D | RootSceneImportResult>obj, options) // todo update references in scene update event
break
case 'config':
if (options?.importConfig !== false) await this.viewer.importConfig(<ISerializedConfig>obj)
break
default:
// legacy
if (obj.type && typeof obj.type === 'string' && (Array.isArray((obj as any).plugins) ||
(obj as any).type === 'ThreeViewer' || this.viewer.getPlugin((obj as any).type))) {
await this.viewer.importConfig(<ISerializedConfig>obj)
}
break
}
this.dispatchEvent({type: 'loadAsset', data: obj})
if (Array.isArray(ret)) ret.push(r)
else ret = r as T
}
return ret || []
}
/**
* same as {@link loadImported}
* @param imported
* @param options
*/
async addProcessedAssets<T extends ImportResult | undefined = ImportResult>(imported: (T | undefined)[], options?: AddAssetOptions): Promise<(T | undefined)[]> {
return this.loadImported(imported, options)
}
async addAssetSingle<T extends ImportResult = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T | undefined> {
return !asset ? undefined : (await this.addAsset<T>(asset, options))?.[0]
}
// processAndAddObjects
async addRaw<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
const r = await this.importer.processRaw<T>(res, options)
return this.loadImported<T[]>(r, options)
}
async addRawSingle<T extends ImportResult | undefined = ImportResult | undefined>(res: T, options: AddRawOptions = {}): Promise<T | undefined> {
return (await this.addRaw<T>(res, options))?.[0]
}
private _sceneUpdated<T extends keyof ISceneEventMap>(ev: BaseEvent<T> & ISceneEventMap[T]) { // todo: check if objects are added some other way.
if (ev.type === 'addSceneObject') {
const event = ev as ISceneEventMap['addSceneObject']
const target = event.object as ImportResult
switch (target.assetType) {
case 'material':
this.materials.registerMaterial(<IMaterial>target)
break
case 'texture':
break
case 'model':
case 'light':
case 'camera':
break
default:
break
}
} else if (ev.type === 'materialChanged') {
const event = ev as ISceneEventMap['materialChanged']
const target = event.material as IMaterial | IMaterial[] | undefined
const targets = Array.isArray(target) ? target : target ? [target] : []
for (const t of targets) {
this.materials.registerMaterial(t)
}
} else if (ev.type === 'beforeDeserialize') {
const event = ev as ISceneEventMap['beforeDeserialize']
// object/material/texture to be deserialized
const data = event.data as any
const meta = event.meta
if (!data.metadata) {
console.warn('Invalid data(no metadata)', data)
}
if (event.material) {
if (data.metadata?.type !== 'Material') {
console.warn('Invalid material data', data)
}
JSONMaterialLoader.DeserializeMaterialJSON(data, this.viewer, meta, event.material).then(() => {
//
})
}
} else {
console.error('Unexpected')
}
}
dispose() {
this.importer.dispose()
this.materials.dispose()
this.processState.clear()
this.viewer.scene.removeEventListener('addSceneObject', this._sceneUpdated)
this.viewer.scene.removeEventListener('materialChanged', this._sceneUpdated)
this.exporter.dispose()
}
protected _addImporters() {
const viewer = this.viewer
if (!viewer) return
// todo fix - loading manager getHandler matches backwards?
const importers: Importer[] = [
new Importer(SVGTextureLoader, ['svg', 'data:image/svg'], ['image/svg+xml'], false), // todo: use ImageBitmapLoader if supported (better performance)
new Importer(TextureLoader, ['webp', 'png', 'jpeg', 'jpg', 'ico', 'data:image', 'avif', 'bmp', 'gif', 'tiff'], [
'image/webp', 'image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/avif',
], false), // todo: use ImageBitmapLoader if supported (better performance)
new Importer<JSONMaterialLoader>(JSONMaterialLoader,
['mat', ...this.materials.templates.map(t => t.typeSlug!).filter(v => v)], // todo add others
[], false, (loader) => {
if (loader) loader.viewer = this.viewer
return loader
}),
new Importer(class extends RGBELoader {
constructor(manager: LoadingManager) {
super(manager)
this.setDataType(getTextureDataType(viewer.renderManager.renderer))
}
}, ['hdr'], ['image/vnd.radiance'], false),
new Importer(class extends EXRLoader {
constructor(manager: LoadingManager) {
super(manager)
this.setDataType(getTextureDataType(viewer.renderManager.renderer))
}
}, ['exr'], ['image/x-exr'], false),
new Importer(FBXLoader2, ['fbx'], ['model/fbx'], true),
new Importer(ZipLoader, ['zip', 'glbz', 'gltfz'], ['application/zip', 'model/gltf+zip', 'model/zip'], true), // gltfz and glbz are invented zip files with gltf/glb inside along with resources
new Importer(OBJLoader2 as any as Class<ILoader>, ['obj'], ['model/obj'], true),
new Importer(MTLLoader2 as any as Class<ILoader>, ['mtl'], ['model/mtl'], false),
new Importer<GLTFLoader2>(GLTFLoader2, ['gltf', 'glb', 'data:model/gltf', 'data:model/glb'], ['model/gltf', 'model/gltf+json', 'model/gltf-binary', 'model/glb'], true, (l, _, i) => l?.setup(this.viewer, i.extensions)),
new Importer(DRACOLoader2, ['drc'], ['model/mesh+draco', 'model/drc'], true),
new Importer(VideoTextureLoader, ['mp4', 'ogg', 'mov', 'webm', 'data:video'], ['video/mp4', 'video/ogg', 'video/quicktime', 'video/webm'], true),
]
this.importer.addImporter(...importers)
}
private _gltfExporter = {
ext: ['gltf', 'glb'],
extensions: [] as (typeof GLTFExporter2.ExportExtensions)[number][],
ctor: (_, exporter) => {
const ex = new GLTFExporter2()
// This should be added at the end.
ex.setup(this.viewer, exporter.extensions)
return ex
},
} satisfies IExporter
protected _addExporters() {
const exporters: IExporter[] = [this._gltfExporter]
this.exporter.addExporter(...exporters)
}
protected _setupObjectProcess() {
this.importer.addEventListener('processRaw', (event) => {
const obj = event.data as IObject3D
if (obj && obj.isObject3D) {
this._loadObjectDependencies(obj)
return
}
// console.log('preprocess mat', mat)
const mat = event.data as IMaterial
if (!mat || !mat.isMaterial || !mat.uuid) return
this.materials.registerMaterial(mat)
})
this.importer.addEventListener('processRawStart', (event) => {
// console.log('preprocess mat', mat)
const res = event.data!
const options = event.options! as ProcessRawOptions
// if (!res.assetType) {
// if (res.isBufferGeometry) { // for eg stl todo
// res = new Mesh(res, new MeshStandardMaterial())
// }
// if (res.isObject3D) {
// }
// }
if (res.isObject3D) {
const cameras: Camera[] = []
const lights: Light[] = []
res.traverse((obj: any) => {
if (obj.material) {
const materials = Array.isArray(obj.material) ? obj.material : [obj.material]
const newMaterials = []
for (const material of materials) {
const mat = this.materials.convertToIMaterial(material, {createFromTemplate: options.replaceMaterials !== false}) || material
mat.uuid = material.uuid
mat.userData.uuid = material.uuid
newMaterials.push(mat)
}
if (Array.isArray(obj.material)) obj.material = newMaterials
else obj.material = newMaterials[0]
}
if (obj.isCamera) cameras.push(obj)
if (obj.isLight) lights.push(obj)
})
for (const camera of cameras) {
if ((camera as Partial<ICamera>).assetType === 'camera') continue
// todo: OrthographicCamera
if (!camera.parent || options.replaceCameras === false) {
iCameraCommons.upgradeCamera.call(camera)
} else {
const newCamera: ICamera = (camera as any).iCamera ??
!(camera as Partial<ICamera>).isOrthographicCamera ?
new PerspectiveCamera2('', this.viewer.canvas) :
new OrthographicCamera2('', this.viewer.canvas)
if (camera === newCamera) continue
camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera)
newCamera.parent = camera.parent as any
newCamera.copy(camera as any)
camera.parent = null
;(newCamera as any).uuid = camera.uuid
newCamera.userData.uuid = camera.uuid
;(camera as any).iCamera = newCamera
// console.log('replacing camera', camera, newCamera)
}
}
for (const light of lights) {
if ((light as ILight).assetType === 'light') continue
if (!light.parent || options.replaceLights === false) {
iLightCommons.upgradeLight.call(light)
} else {
const newLight: ILight | undefined = (light as any).iLight ??
(light as any).isDirectionalLight ? new DirectionalLight2() :
(light as any).isPointLight ? new PointLight2() :
(light as any).isSpotLight ? new SpotLight2() :
(light as any).isAmbientLight ? new AmbientLight2() :
(light as any).isHemisphereLight ? new HemisphereLight2() :
(light as any).isRectAreaLight ? new RectAreaLight2() :
undefined
if (light === newLight || !newLight) continue
light.parent.children.splice(light.parent.children.indexOf(light), 1, newLight)
newLight.parent = light.parent as any
newLight.copy(light as any)
light.parent = null
;(newLight as any).uuid = light.uuid
newLight.userData.uuid = light.uuid
;(light as any).iLight = newLight
}
}
iObjectCommons.upgradeObject3D.call(res)
} else if (res.isMaterial) {
if (!res.assetType) iMaterialCommons.upgradeMaterial.call(res)
// todo update res by generating new material?
} else if (res.isTexture) {
upgradeTexture.call(res)
if (event?.options?.generateMipmaps !== undefined)
res.generateMipmaps = event?.options.generateMipmaps
if (!res.generateMipmaps && !res.isRenderTargetTexture) { // todo: do we need to check more?
res.minFilter = res.minFilter === LinearMipmapLinearFilter ? LinearFilter : res.minFilter
res.magFilter = res.magFilter === LinearMipmapLinearFilter ? LinearFilter : res.magFilter
}
}
// todo other asset/object types?
})
}
/**
* Load the embedded `rootPath` dependencies within this object
* @param object
* @private
*/
private async _loadObjectDependencies(object: IObject3D) {
const deps = [] as IObject3D[]
if (!object.traverseModels) {
this.viewer.console.error('AssetManager - Object not upgraded, cannot load dependencies', object)
return
}
object.traverseModels && object.traverseModels(m => {
if (m.userData.rootPathRefresh && !m._rootPathRefreshed && !m._rootPathRefreshing && m.userData.rootPath) {
deps.push(m)
return false // to not traverse children
}
return true
}, {visible: false, widgets: true})
const pms = deps.map(async m => {
m._rootPathRefreshing = true
const rootPath = m.userData.rootPath
if (!rootPath) return null
const rootPathOptions = m.userData.rootPathOptions
const res = await this.importer.import(rootPath, {
...rootPathOptions,
})
if (!res) {
throw new Error(`Unable to load asset from url - ${rootPath}`)
}
return res
})
const r = await Promise.allSettled(pms)
// console.log(r)
for (let i = 0; i < r.length; i++) {
const res = r[i]
const obj = deps[i]
delete obj._rootPathRefreshing
obj._rootPathRefreshed = true
if (res.status === 'rejected') {
this.viewer.console.error(`ThreeViewer - Failed to load root path for object ${obj.name}`, res.reason)
continue
}
if (res.status !== 'fulfilled') continue
const models = res.value
if (!models || !models.length) continue
const model = models.find(m => m?.isObject3D)
if (!model) {
this.viewer.console.warn('AssetManager - No valid model found in root path', res.value)
continue
}
const others = models.filter(m => m && m !== model)
const parent = obj.parent
const newIndex = parent ? parent.children.indexOf(obj) : -1
if (parent) obj.removeFromParent()
this.viewer.object3dManager.unregisterObject(obj)
if (!model.isObject3D) {
this.viewer.console.warn('Non model dependency loaded. Not fully supported yet.')
// todo?
continue
}
if (!parent) {
this.viewer.console.error('AssetManager - Unexpected error, no parent found for object when loading dependency', obj)
// parent = this.viewer.scene.modelRoot
continue
}
if (model._copyFromEmbedded) model._copyFromEmbedded(obj) // todo better name, document this in IObject3D or ImportResult?
else {
obj.matrix.decompose(model.position, model.quaternion, model.scale)
model.name = obj.name // copy name
model.userData = {...obj.userData, ...model.userData} // merge userData
model._rootPathRefreshed = true // mark as refreshed
// @ts-expect-error force update
model.uuid = obj.uuid
}
parent.add(model as IObject3D)
const newIndex2 = parent.children.indexOf(model as IObject3D)
if (newIndex >= 0 && newIndex2 >= 0 && newIndex !== newIndex2) {
parent.children.splice(newIndex2, 1)
parent.children.splice(newIndex, 0, model as IObject3D) // add at new index
}
if (others.length) {
for (const other of others) {
if (other?.isObject3D) {
parent.add(other as Object3D)
} else {
this.viewer.console.warn('Non model dependency loaded. Not fully supported yet.', other)
}
}
}
}
}
// region process state
/**
* State of download/upload/process/other processes in the viewer.
* Subscribes to importer and exporter by default, more can be added by plugins like {@link FileTransferPlugin}
*/
processState: Map<string, {state: string, progress?: number | undefined}> = new Map()
/**
* Set process state for a path
* Progress should be a number between 0 and 100
* Pass undefined in value to remove the state
* @param path
* @param value
*/
setProcessState(path: string, value: {state: string, progress?: number | undefined} | undefined) {
if (value === undefined) this.processState.delete(path)
else this.processState.set(path, value)
this.dispatchEvent({type: 'processStateUpdate'})
}
protected _setupProcessState() {
this.importer.addEventListener('importFile', (data: any) => {
this.setProcessState(data.path, data.state !== 'done' ? {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
} : undefined)
})
this.importer.addEventListener('processRawStart', (data: any) => {
this.setProcessState(data.path, {
state: 'processing',
progress: undefined,
})
})
this.importer.addEventListener('processRaw', (data: any) => {
this.setProcessState(data.path, undefined)
})
this.exporter.addEventListener('exportFile', (data: any) => {
this.setProcessState(data.obj.name, data.state !== 'done' ? {
state: data.state,
progress: data.progress ? data.progress * 100 : undefined,
} : undefined)
})
}
// endregion
// region glTF extensions registration helpers
gltfExtensions: {
name: string
import: (parser: GLTFParser) => GLTFLoaderPlugin,
export: (parser: GLTFWriter2) => GLTFExporterPlugin,
textures?: Record<string, string|number> // see GLTFDracoExportPlugin
}[] = []
protected _setupGltfExtensions() {
this.importer.addEventListener('loaderCreate', this._loaderCreate as any)
this.viewer.forPlugin('GLTFDracoExportPlugin', (p)=> {
if (!p.addExtension) return
for (const gltfExtension of this.gltfExtensions) {
p.addExtension(gltfExtension.name, gltfExtension.textures)
}
})
}
protected _loaderCreate({loader}: {loader: GLTFLoader2}) {
if (!loader.isGLTFLoader2) return
for (const gltfExtension of this.gltfExtensions) {
loader.register(gltfExtension.import)
}
}
registerGltfExtension(ext: AssetManager['gltfExtensions'][number]) {
const ext1 = this.gltfExtensions.findIndex(e => e.name === ext.name)
if (ext1 >= 0) this.gltfExtensions.splice(ext1, 1)
this.gltfExtensions.push(ext)
this._gltfExporter.extensions.push(ext.export)
const exporter2 = this.exporter.getExporter('gltf', 'glb')
if (exporter2 && exporter2 !== this._gltfExporter)
exporter2.extensions?.push(ext.export)
}
unregisterGltfExtension(name: string) {
const ind = this.gltfExtensions.findIndex(e => e.name === name)
if (ind < 0) return
this.gltfExtensions.splice(ind, 1)
const ind1 = this._gltfExporter.extensions.findIndex(e => e.name === name)
if (ind1 >= 0) this._gltfExporter.extensions.splice(ind1, 1)
const exporter2 = this.exporter.getExporter('gltf', 'glb')
if (exporter2?.extensions && exporter2 !== this._gltfExporter) {
const ind2 = exporter2.extensions.findIndex(e => e.name === name)
if (ind2 >= 0) exporter2.extensions?.splice(ind2, 1)
}
}
// endregion
// region deprecated
/**
* @deprecated use addRaw instead
* @param res
* @param options
*/
async addImported<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
console.error('addImported is deprecated, use addRaw instead')
return this.addRaw(res, options)
}
/**
* @deprecated use addAsset instead
* @param path
* @param options
*/
public async addFromPath(path: string, options: ImportAddOptions = {}): Promise<any[]> {
console.error('addFromPath is deprecated, use addAsset instead')
return this.addAsset(path, options)
}
/**
* @deprecated use {@link ThreeViewer.exportConfig} instead
* @param binary - if set to false, encodes all the array buffers to base64
*/
exportViewerConfig(binary = true): Record<string, any> {
if (!this.viewer) return {}
console.error('exportViewerConfig is deprecated, use viewer.toJSON instead')
return this.viewer.toJSON(binary, undefined)
}
/**
* @deprecated use {@link ThreeViewer.exportPluginsConfig} instead
* @param filter
*/
exportPluginPresets(filter?: string[]) {
console.error('exportPluginPresets is deprecated, use viewer.exportPluginsConfig instead')
return this.viewer?.exportPluginsConfig(filter)
}
/**
* @deprecated use {@link ThreeViewer.exportPluginConfig} instead
* @param plugin
*/
exportPluginPreset(plugin: IViewerPlugin) {
console.error('exportPluginPreset is deprecated, use viewer.exportPluginConfig instead')
return this.viewer?.exportPluginConfig(plugin)
}
/**
* @deprecated use {@link ThreeViewer.importPluginConfig} instead
* @param json
* @param plugin
*/
async importPluginPreset(json: any, plugin?: IViewerPlugin) {
console.error('importPluginPreset is deprecated, use viewer.importPluginConfig instead')
return this.viewer?.importPluginConfig(json, plugin)
}
// todo continue from here by moving functions to the viewer.
/**
* @deprecated use {@link ThreeViewer.importConfig} instead
* @param viewerConfig
*/
async importViewerConfig(viewerConfig: any) {
return this.viewer?.importConfig(viewerConfig)
}
/**
* @deprecated use {@link ThreeViewer.fromJSON} instead
* @param viewerConfig
*/
applyViewerConfig(viewerConfig: any, resources?: any) {
console.error('applyViewerConfig is deprecated, use viewer.fromJSON instead')
return this.viewer?.fromJSON(viewerConfig, resources)
}
/**
* @deprecated moved to {@link ThreeViewer.loadConfigResources}
* @param json
* @param extraResources - preloaded resources in the format of viewer config resources.
*/
async importConfigResources(json: any, extraResources?: any) {
if (!this.importer) throw 'Importer not initialized yet.'
if (json.__isLoadedResources) return json
return this.viewer?.loadConfigResources(json, extraResources)
}
/**
* @deprecated not a plugin anymore
*/
static readonly PluginType = 'AssetManager'
// endregion
}