threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

AssetManager.ts 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. import {ImportAssetOptions, ImportResult, ProcessRawOptions, RootSceneImportResult} from './IAssetImporter'
  2. import {
  3. BaseEvent,
  4. Cache as threeCache,
  5. Camera,
  6. EventDispatcher,
  7. Light,
  8. LinearFilter,
  9. LinearMipmapLinearFilter,
  10. LoadingManager,
  11. Object3D,
  12. PerspectiveCamera,
  13. TextureLoader,
  14. } from 'three'
  15. import {ISerializedConfig, IViewerPlugin, ThreeViewer} from '../viewer'
  16. import {AssetImporter} from './AssetImporter'
  17. import {generateUUID, getTextureDataType, overrideThreeCache} from '../three'
  18. import {IAsset} from './IAsset'
  19. import {
  20. AddObjectOptions,
  21. AmbientLight2,
  22. DirectionalLight2,
  23. HemisphereLight2,
  24. ICamera,
  25. iCameraCommons,
  26. ILight,
  27. iLightCommons,
  28. IMaterial,
  29. iMaterialCommons,
  30. IObject3D,
  31. iObjectCommons,
  32. ISceneEvent,
  33. ITexture,
  34. PerspectiveCamera2,
  35. PointLight2,
  36. RectAreaLight2,
  37. SpotLight2,
  38. upgradeTexture,
  39. } from '../core'
  40. import {Importer} from './Importer'
  41. import {MaterialManager} from './MaterialManager'
  42. import {DRACOLoader2, GLTFLoader2, JSONMaterialLoader, MTLLoader2, OBJLoader2, ZipLoader} from './import'
  43. import {RGBELoader} from 'three/examples/jsm/loaders/RGBELoader.js'
  44. import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader.js'
  45. import {EXRLoader} from 'three/examples/jsm/loaders/EXRLoader.js'
  46. import {Class, ValOrArr} from 'ts-browser-helpers'
  47. import {ILoader} from './IImporter'
  48. import {AssetExporter} from './AssetExporter'
  49. import {IExporter} from './IExporter'
  50. import {GLTFExporter2} from './export'
  51. import {legacySeparateMapSamplerUVFix} from '../utils/legacy'
  52. export interface AssetManagerOptions{
  53. /**
  54. * simple memory based cache for downloaded files, default = false
  55. */
  56. simpleCache?: boolean
  57. /**
  58. * Cache Storage for downloaded files, can use with `caches.open`
  59. * When true and by default uses `caches.open('threepipe-assetmanager')`, set to false to disable
  60. * @default true
  61. */
  62. storage?: Cache | Storage | boolean
  63. }
  64. export interface AddAssetOptions extends AddObjectOptions{
  65. /**
  66. * Automatically set any loaded HDR, EXR file as the scene environment map
  67. * @default true
  68. */
  69. autoSetEnvironment?: boolean
  70. /**
  71. * Automatically set any loaded image(ITexture) file as the scene background
  72. */
  73. autoSetBackground?: boolean
  74. }
  75. export type ImportAddOptions = ImportAssetOptions & AddAssetOptions
  76. export type AddRawOptions = ProcessRawOptions & AddAssetOptions
  77. /**
  78. * Asset Manager
  79. *
  80. * Utility class to manage import, export, and material management.
  81. * @category Asset Manager
  82. */
  83. export class AssetManager extends EventDispatcher<BaseEvent&{data?: ImportResult}, 'loadAsset'|'processStateUpdate'> {
  84. readonly viewer: ThreeViewer
  85. readonly importer: AssetImporter
  86. readonly exporter: AssetExporter
  87. readonly materials: MaterialManager
  88. private _storage?: Cache | Storage
  89. get storage() {
  90. return this._storage
  91. }
  92. constructor(viewer: ThreeViewer, {simpleCache = false, storage}: AssetManagerOptions = {}) {
  93. super()
  94. this._sceneUpdated = this._sceneUpdated.bind(this)
  95. this.addAsset = this.addAsset.bind(this)
  96. this.addRaw = this.addRaw.bind(this)
  97. this.addImported = this.addImported.bind(this)
  98. this.importer = new AssetImporter(!!viewer.getPlugin('debug'))
  99. this.exporter = new AssetExporter()
  100. this.materials = new MaterialManager()
  101. this.viewer = viewer
  102. this.viewer.scene.addEventListener('addSceneObject', this._sceneUpdated)
  103. this.viewer.scene.addEventListener('materialChanged', this._sceneUpdated)
  104. this.viewer.scene.addEventListener('beforeDeserialize', this._sceneUpdated)
  105. this._initCacheStorage(simpleCache, storage ?? true)
  106. this._setupObjectProcess()
  107. this._setupProcessState()
  108. this._addImporters()
  109. this._addExporters()
  110. }
  111. async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAddOptions): Promise<(T | undefined)[]> {
  112. if (!this.importer || !this.viewer) return []
  113. const imported = await this.importer.import<T>(assetOrPath, options)
  114. if (!imported) {
  115. const path = typeof assetOrPath === 'string' ? assetOrPath : (assetOrPath as IAsset)?.path
  116. if (path && !path.split('?')[0].endsWith('.vjson'))
  117. console.warn('Threepipe AssetManager - Unable to import', assetOrPath, imported)
  118. return []
  119. }
  120. return this.loadImported<(T | undefined)[]>(imported, options)
  121. }
  122. // materials: IMaterial[] = []
  123. // textures: ITexture[] = []
  124. // todo move this function to viewer
  125. async loadImported<T extends ValOrArr<ImportResult | undefined> = ImportResult>(imported: T, {
  126. autoSetEnvironment = true,
  127. autoSetBackground = false,
  128. ...options
  129. }: AddAssetOptions = {}): Promise<T | never[]> {
  130. const arr: (ImportResult | undefined)[] = Array.isArray(imported) ? imported : [imported]
  131. let ret: T = Array.isArray(imported) ? [] : undefined as any
  132. if (options?.importConfig !== false) {
  133. const config = arr.find(v => v?.assetType === 'config') || arr.find(v=>v && !!v.importedViewerConfig)?.importedViewerConfig
  134. if (config) legacySeparateMapSamplerUVFix(config, arr.filter(a=>a?.isObject3D) as Object3D[])
  135. }
  136. for (const obj of arr) {
  137. if (!obj) {
  138. if (Array.isArray(ret)) ret.push(undefined)
  139. continue
  140. }
  141. let r = obj
  142. switch (obj.assetType) {
  143. case 'material':
  144. this.materials.registerMaterial(<IMaterial>obj)
  145. break
  146. case 'texture':
  147. if (autoSetEnvironment && (
  148. obj.__rootPath?.endsWith('.hdr') || obj.__rootPath?.endsWith('.exr')
  149. )) this.viewer.scene.environment = <ITexture>obj
  150. if (autoSetBackground) this.viewer.scene.background = <ITexture>obj
  151. break
  152. case 'model':
  153. case 'light':
  154. case 'camera':
  155. r = await this.viewer.addSceneObject(<IObject3D | RootSceneImportResult>obj, options) // todo update references in scene update event
  156. break
  157. case 'config':
  158. if (options?.importConfig !== false) await this.viewer.importConfig(<ISerializedConfig>obj)
  159. break
  160. default:
  161. // legacy
  162. if (obj.type && typeof obj.type === 'string' && (Array.isArray((obj as any).plugins) ||
  163. (obj as any).type === 'ThreeViewer' || this.viewer.getPlugin((obj as any).type))) {
  164. await this.viewer.importConfig(<ISerializedConfig>obj)
  165. }
  166. break
  167. }
  168. this.dispatchEvent({type: 'loadAsset', data: obj})
  169. if (Array.isArray(ret)) ret.push(r)
  170. else ret = r as T
  171. }
  172. return ret || []
  173. }
  174. /**
  175. * same as {@link loadImported}
  176. * @param imported
  177. * @param options
  178. */
  179. async addProcessedAssets<T extends ImportResult | undefined = ImportResult>(imported: (T | undefined)[], options?: AddAssetOptions): Promise<(T | undefined)[]> {
  180. return this.loadImported(imported, options)
  181. }
  182. async addAssetSingle<T extends ImportResult = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T | undefined> {
  183. return !asset ? undefined : (await this.addAsset<T>(asset, options))?.[0]
  184. }
  185. // processAndAddObjects
  186. async addRaw<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
  187. const r = await this.importer.processRaw<T>(res, options)
  188. return this.loadImported<T[]>(r, options)
  189. }
  190. async addRawSingle<T extends ImportResult | undefined = ImportResult | undefined>(res: T, options: AddRawOptions = {}): Promise<T | undefined> {
  191. return (await this.addRaw<T>(res, options))?.[0]
  192. }
  193. private _sceneUpdated(event: ISceneEvent) { // todo: check if objects are added some other way.
  194. if (event.type === 'addSceneObject') {
  195. const target = event.object as ImportResult
  196. switch (target.assetType) {
  197. case 'material':
  198. this.materials.registerMaterial(<IMaterial>target)
  199. break
  200. case 'texture':
  201. break
  202. case 'model':
  203. case 'light':
  204. case 'camera':
  205. break
  206. default:
  207. break
  208. }
  209. } else if (event.type === 'materialChanged') {
  210. const target = event.material as IMaterial | IMaterial[] | undefined
  211. const targets = Array.isArray(target) ? target : target ? [target] : []
  212. for (const t of targets) {
  213. this.materials.registerMaterial(t)
  214. }
  215. } else if (event.type === 'beforeDeserialize') {
  216. // object/material/texture to be deserialized
  217. const data = event.data
  218. const meta = event.meta
  219. if (!data.metadata) {
  220. console.warn('Invalid data(no metadata)', data)
  221. }
  222. if (event.material) {
  223. if (data.metadata?.type !== 'Material') {
  224. console.warn('Invalid material data', data)
  225. }
  226. JSONMaterialLoader.DeserializeMaterialJSON(data, this.viewer, meta, event.material).then(() => {
  227. //
  228. })
  229. }
  230. } else {
  231. console.error('Unexpected')
  232. }
  233. }
  234. dispose() {
  235. this.importer.dispose()
  236. this.materials.dispose()
  237. this.processState.clear()
  238. this.viewer.scene.removeEventListener('addSceneObject', this._sceneUpdated)
  239. this.viewer.scene.removeEventListener('materialChanged', this._sceneUpdated)
  240. this.exporter.dispose()
  241. }
  242. protected _addImporters() {
  243. const viewer = this.viewer
  244. if (!viewer) return
  245. const importers: Importer[] = [
  246. new Importer(TextureLoader, ['webp', 'png', 'jpeg', 'jpg', 'svg', 'ico', 'data:image', 'avif'], [
  247. 'image/webp', 'image/png', 'image/jpeg', 'image/svg+xml', 'image/gif', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/avif',
  248. ], false), // todo: use ImageBitmapLoader if supported (better performance)
  249. new Importer<JSONMaterialLoader>(JSONMaterialLoader,
  250. ['mat', ...this.materials.templates.map(t => t.typeSlug!).filter(v => v)], // todo add others
  251. [], false, (loader) => {
  252. if (loader) loader.viewer = this.viewer
  253. return loader
  254. }),
  255. new Importer(class extends RGBELoader {
  256. constructor(manager: LoadingManager) {
  257. super(manager)
  258. this.setDataType(getTextureDataType(viewer.renderManager.renderer))
  259. }
  260. }, ['hdr'], ['image/vnd.radiance'], false),
  261. new Importer(class extends EXRLoader {
  262. constructor(manager: LoadingManager) {
  263. super(manager)
  264. this.setDataType(getTextureDataType(viewer.renderManager.renderer))
  265. }
  266. }, ['exr'], ['image/x-exr'], false),
  267. new Importer(FBXLoader, ['fbx'], ['model/fbx'], true),
  268. 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
  269. new Importer(OBJLoader2 as any as Class<ILoader>, ['obj'], ['model/obj'], true),
  270. new Importer(MTLLoader2 as any as Class<ILoader>, ['mtl'], ['model/mtl'], false),
  271. 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)),
  272. new Importer(DRACOLoader2, ['drc'], ['model/mesh+draco', 'model/drc'], true),
  273. ]
  274. this.importer.addImporter(...importers)
  275. }
  276. protected _addExporters() {
  277. const exporters: IExporter[] = [
  278. {
  279. ext: ['gltf', 'glb'], extensions: [], ctor: (_, exporter) => {
  280. const ex = new GLTFExporter2()
  281. // This should be added at the end.
  282. ex.setup(this.viewer, exporter.extensions)
  283. return ex
  284. },
  285. },
  286. ]
  287. this.exporter.addExporter(...exporters)
  288. }
  289. private _initCacheStorage(simpleCache?: boolean, storage?: Cache | Storage | boolean) {
  290. if (storage === true && window?.caches) {
  291. window.caches.open?.('threepipe-assetmanager').then(c => {
  292. this._initCacheStorage(simpleCache, c)
  293. this._storage = c
  294. })
  295. return
  296. }
  297. if (simpleCache || storage) {
  298. // three.js built-in simple memory cache. used in FileLoader.js todo: use local storage somehow
  299. if (simpleCache) threeCache.enabled = true
  300. if (storage && window.Cache && typeof window.Cache === 'function' && storage instanceof window.Cache) {
  301. overrideThreeCache(storage)
  302. // todo: clear cache
  303. }
  304. }
  305. this._storage = typeof storage === 'boolean' ? undefined : storage
  306. }
  307. protected _setupObjectProcess() {
  308. this.importer.addEventListener('processRaw', (event) => {
  309. // console.log('preprocess mat', mat)
  310. const mat = event.data as IMaterial
  311. if (!mat || !mat.isMaterial || !mat.uuid) return
  312. if (this.materials?.findMaterial(mat.uuid)) {
  313. console.warn('imported material uuid already exists, creating new uuid')
  314. mat.uuid = generateUUID()
  315. if (mat.userData.uuid) mat.userData.uuid = mat.uuid
  316. }
  317. // todo: check for name exists also
  318. this.materials.registerMaterial(mat)
  319. })
  320. this.importer.addEventListener('processRawStart', (event) => {
  321. // console.log('preprocess mat', mat)
  322. const res = event.data!
  323. const options = event.options! as ProcessRawOptions
  324. // if (!res.assetType) {
  325. // if (res.isBufferGeometry) { // for eg stl todo
  326. // res = new Mesh(res, new MeshStandardMaterial())
  327. // }
  328. // if (res.isObject3D) {
  329. // }
  330. // }
  331. if (res.isObject3D) {
  332. const cameras: Camera[] = []
  333. const lights: Light[] = []
  334. res.traverse((obj: any) => {
  335. if (obj.material) {
  336. const materials = Array.isArray(obj.material) ? obj.material : [obj.material]
  337. const newMaterials = []
  338. for (const material of materials) {
  339. const mat = this.materials.convertToIMaterial(material, {createFromTemplate: options.replaceMaterials !== false}) || material
  340. mat.uuid = material.uuid
  341. mat.userData.uuid = material.uuid
  342. newMaterials.push(mat)
  343. }
  344. if (Array.isArray(obj.material)) obj.material = newMaterials
  345. else obj.material = newMaterials[0]
  346. }
  347. if (obj.isCamera) cameras.push(obj)
  348. if (obj.isLight) lights.push(obj)
  349. })
  350. for (const camera of cameras) {
  351. if ((camera as PerspectiveCamera2).assetType === 'camera') continue
  352. // todo: OrthographicCamera
  353. if (!(camera as PerspectiveCamera).isPerspectiveCamera || !camera.parent || options.replaceCameras === false) {
  354. iCameraCommons.upgradeCamera.call(camera)
  355. } else {
  356. const newCamera: ICamera = (camera as any).iCamera ??
  357. new PerspectiveCamera2('', this.viewer.canvas)
  358. if (camera === newCamera) continue
  359. camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera)
  360. newCamera.parent = camera.parent as any
  361. newCamera.copy(camera as any)
  362. camera.parent = null
  363. ;(newCamera as any).uuid = camera.uuid
  364. newCamera.userData.uuid = camera.uuid
  365. ;(camera as any).iCamera = newCamera
  366. // console.log('replacing camera', camera, newCamera)
  367. }
  368. }
  369. for (const light of lights) {
  370. if ((light as ILight).assetType === 'light') continue
  371. if (!light.parent || options.replaceLights === false) {
  372. iLightCommons.upgradeLight.call(light)
  373. } else {
  374. const newLight: ILight | undefined = (light as any).iLight ??
  375. (light as any).isDirectionalLight ? new DirectionalLight2() :
  376. (light as any).isPointLight ? new PointLight2() :
  377. (light as any).isSpotLight ? new SpotLight2() :
  378. (light as any).isAmbientLight ? new AmbientLight2() :
  379. (light as any).isHemisphereLight ? new HemisphereLight2() :
  380. (light as any).isRectAreaLight ? new RectAreaLight2() :
  381. undefined
  382. if (light === newLight || !newLight) continue
  383. light.parent.children.splice(light.parent.children.indexOf(light), 1, newLight)
  384. newLight.parent = light.parent as any
  385. newLight.copy(light as any)
  386. light.parent = null
  387. ;(newLight as any).uuid = light.uuid
  388. newLight.userData.uuid = light.uuid
  389. ;(light as any).iLight = newLight
  390. }
  391. }
  392. iObjectCommons.upgradeObject3D.call(res)
  393. } else if (res.isMaterial) {
  394. iMaterialCommons.upgradeMaterial.call(res)
  395. // todo update res by generating new material?
  396. } else if (res.isTexture) {
  397. upgradeTexture.call(res)
  398. if (event?.options?.generateMipmaps !== undefined)
  399. res.generateMipmaps = event?.options.generateMipmaps
  400. if (!res.generateMipmaps && !res.isRenderTargetTexture) { // todo: do we need to check more?
  401. res.minFilter = res.minFilter === LinearMipmapLinearFilter ? LinearFilter : res.minFilter
  402. res.magFilter = res.magFilter === LinearMipmapLinearFilter ? LinearFilter : res.magFilter
  403. }
  404. }
  405. // todo other asset/object types?
  406. })
  407. }
  408. /**
  409. * State of download/upload/process/other processes in the viewer.
  410. * Subscribes to importer and exporter by default, more can be added by plugins like {@link FileTransferPlugin}
  411. */
  412. processState: Map<string, {state: string, progress?: number | undefined}> = new Map()
  413. /**
  414. * Set process state for a path
  415. * Progress should be a number between 0 and 100
  416. * Pass undefined in value to remove the state
  417. * @param path
  418. * @param value
  419. */
  420. setProcessState(path: string, value: {state: string, progress?: number | undefined} | undefined) {
  421. if (value === undefined) this.processState.delete(path)
  422. else this.processState.set(path, value)
  423. this.dispatchEvent({type: 'processStateUpdate'})
  424. }
  425. protected _setupProcessState() {
  426. this.importer.addEventListener('importFile', (data: any) => {
  427. this.setProcessState(data.path, data.state !== 'done' ? {
  428. state: data.state,
  429. progress: data.progress ? data.progress * 100 : undefined,
  430. } : undefined)
  431. })
  432. this.importer.addEventListener('processRawStart', (data: any) => {
  433. this.setProcessState(data.path, {
  434. state: 'processing',
  435. progress: undefined,
  436. })
  437. })
  438. this.importer.addEventListener('processRaw', (data: any) => {
  439. this.setProcessState(data.path, undefined)
  440. })
  441. this.exporter.addEventListener('exportFile', (data: any) => {
  442. this.setProcessState(data.obj.name, data.state !== 'done' ? {
  443. state: data.state,
  444. progress: data.progress ? data.progress * 100 : undefined,
  445. } : undefined)
  446. })
  447. }
  448. // region deprecated
  449. /**
  450. * @deprecated use addRaw instead
  451. * @param res
  452. * @param options
  453. */
  454. async addImported<T extends (ImportResult | undefined) = ImportResult>(res: T | T[], options: AddRawOptions = {}): Promise<(T | undefined)[]> {
  455. console.error('addImported is deprecated, use addRaw instead')
  456. return this.addRaw(res, options)
  457. }
  458. /**
  459. * @deprecated use addAsset instead
  460. * @param path
  461. * @param options
  462. */
  463. public async addFromPath(path: string, options: ImportAddOptions = {}): Promise<any[]> {
  464. console.error('addFromPath is deprecated, use addAsset instead')
  465. return this.addAsset(path, options)
  466. }
  467. /**
  468. * @deprecated use {@link ThreeViewer.exportConfig} instead
  469. * @param binary - if set to false, encodes all the array buffers to base64
  470. */
  471. exportViewerConfig(binary = true): Record<string, any> {
  472. if (!this.viewer) return {}
  473. console.error('exportViewerConfig is deprecated, use viewer.toJSON instead')
  474. return this.viewer.toJSON(binary, undefined)
  475. }
  476. /**
  477. * @deprecated use {@link ThreeViewer.exportPluginsConfig} instead
  478. * @param filter
  479. */
  480. exportPluginPresets(filter?: string[]) {
  481. console.error('exportPluginPresets is deprecated, use viewer.exportPluginsConfig instead')
  482. return this.viewer?.exportPluginsConfig(filter)
  483. }
  484. /**
  485. * @deprecated use {@link ThreeViewer.exportPluginConfig} instead
  486. * @param plugin
  487. */
  488. exportPluginPreset(plugin: IViewerPlugin) {
  489. console.error('exportPluginPreset is deprecated, use viewer.exportPluginConfig instead')
  490. return this.viewer?.exportPluginConfig(plugin)
  491. }
  492. /**
  493. * @deprecated use {@link ThreeViewer.importPluginConfig} instead
  494. * @param json
  495. * @param plugin
  496. */
  497. async importPluginPreset(json: any, plugin?: IViewerPlugin) {
  498. console.error('importPluginPreset is deprecated, use viewer.importPluginConfig instead')
  499. return this.viewer?.importPluginConfig(json, plugin)
  500. }
  501. // todo continue from here by moving functions to the viewer.
  502. /**
  503. * @deprecated use {@link ThreeViewer.importConfig} instead
  504. * @param viewerConfig
  505. */
  506. async importViewerConfig(viewerConfig: any) {
  507. return this.viewer?.importConfig(viewerConfig)
  508. }
  509. /**
  510. * @deprecated use {@link ThreeViewer.fromJSON} instead
  511. * @param viewerConfig
  512. */
  513. applyViewerConfig(viewerConfig: any, resources?: any) {
  514. console.error('applyViewerConfig is deprecated, use viewer.fromJSON instead')
  515. return this.viewer?.fromJSON(viewerConfig, resources)
  516. }
  517. /**
  518. * @deprecated moved to {@link ThreeViewer.loadConfigResources}
  519. * @param json
  520. * @param extraResources - preloaded resources in the format of viewer config resources.
  521. */
  522. async importConfigResources(json: any, extraResources?: any) {
  523. if (!this.importer) throw 'Importer not initialized yet.'
  524. if (json.__isLoadedResources) return json
  525. return this.viewer?.loadConfigResources(json, extraResources)
  526. }
  527. /**
  528. * @deprecated not a plugin anymore
  529. */
  530. static readonly PluginType = 'AssetManager'
  531. // endregion
  532. }