threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AssetExporter.ts 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {BaseEvent, EventDispatcher, WebGLRenderTarget} from 'three'
  2. import {IMaterial, IObject3D, ITexture} from '../core'
  3. import {BlobExt, ExportFileOptions, IAssetExporter, IExporter, IExportParser} from './IExporter'
  4. import {EXRExporter2, SimpleJSONExporter, SimpleTextExporter} from './export'
  5. import {IRenderTarget} from '../rendering'
  6. /**
  7. * Asset Exporter
  8. *
  9. * Utility class to export objects, materials, textures, render targets, etc.
  10. * Used in {@link AssetManager} to export assets.
  11. * @category Asset Manager
  12. */
  13. export class AssetExporter extends EventDispatcher<BaseEvent, 'exporterCreate' | 'exportFile'> implements IAssetExporter {
  14. readonly exporters: IExporter[] = [
  15. {ctor: ()=>new SimpleJSONExporter(), ext: ['json']},
  16. {ctor: ()=>new SimpleTextExporter(), ext: ['txt', 'text']},
  17. {ctor: ()=>new EXRExporter2(), ext: ['exr']},
  18. // {ctor: ()=>new EXRExporter2(), ext: ['png', 'jpeg', 'webp']}, // todo
  19. // {ctor: ()=>new GLTFDracoExporter(), ext: ['gltf', 'glb']},
  20. ]
  21. addExporter(...exporters: IExporter[]) {
  22. for (const exporter of exporters) {
  23. if (this.exporters.includes(exporter)) {
  24. console.warn('Exporter already added', exporter)
  25. return
  26. }
  27. this.exporters.push(exporter)
  28. }
  29. }
  30. removeExporter(...exporters: IExporter[]) {
  31. for (const exporter of exporters) {
  32. const i = this.exporters.indexOf(exporter)
  33. if (i >= 0) this.exporters.splice(i, 1)
  34. }
  35. }
  36. getExporter(...ext: string[]): IExporter|undefined {
  37. return this.exporters.find(e=>e.ext.some(e1=>ext.includes(e1)))
  38. }
  39. constructor() {
  40. super()
  41. }
  42. public async exportObject(obj?: IObject3D|IMaterial|ITexture|IRenderTarget, options: ExportFileOptions = {}): Promise<BlobExt|undefined> {
  43. if (!obj?.assetType) {
  44. console.error('Object has no asset type')
  45. return undefined
  46. }
  47. const excluded: IObject3D[] = []
  48. if (obj.assetType === 'model') {
  49. obj.traverse((o)=>{
  50. if (o.userData.excludeFromExport && o.visible) {
  51. o.visible = false
  52. excluded.push(o)
  53. }
  54. })
  55. }
  56. const blob = await this._exportFile(obj, options)
  57. if (obj.assetType === 'model') {
  58. excluded.forEach((o: any)=>o.visible = true)
  59. }
  60. if ((obj as any)?.userData?.rootSceneModelRoot && options.viewerConfig === false) {
  61. delete (obj as any)!.userData!.__exportViewerConfig
  62. }
  63. return blob
  64. }
  65. // export to blob
  66. private async _exportFile(obj: IObject3D|IMaterial|ITexture|IRenderTarget, options: ExportFileOptions = {}): Promise<BlobExt|undefined> {
  67. // if ((file as any)?.__imported) return (file as any).__imported // todo: cache exports?
  68. let res: BlobExt
  69. try {
  70. this.dispatchEvent({type: 'exportFile', obj, state:'processing', exportOptions: options})
  71. const processed = await this.processBeforeExport(obj, options)
  72. const ext = options.exportExt || processed?.typeExt || processed?.ext
  73. if (!processed || !ext) {
  74. console.error(processed, options, obj)
  75. throw new Error(`Unable to preprocess before export ${ext}`)
  76. }
  77. if (processed.blob) res = processed.blob
  78. else {
  79. const parser = this._getParser(ext)
  80. this.dispatchEvent({type: 'exportFile', obj, state:'exporting'})
  81. res = await parser.parseAsync(processed.obj, {exportExt: processed.ext ?? ext, ...options}) as BlobExt
  82. res.ext = processed.ext
  83. }
  84. this.dispatchEvent({type: 'exportFile', obj, state: 'done'})
  85. } catch (e) {
  86. console.error('AssetExporter: Unable to Export file', obj)
  87. // console.error(e)
  88. this.dispatchEvent({type: 'exportFile', obj, state: 'error', error: e})
  89. throw e
  90. return undefined
  91. }
  92. // if (file) (file as any).__imported = res
  93. return res
  94. }
  95. private _createParser(ext: string): IExportParser {
  96. const exporter = this.exporters.find(e => e.ext.includes(ext))
  97. if (!exporter)
  98. throw new Error(`No exporter found for extension ${ext}`)
  99. const parser = exporter?.ctor(this, exporter)
  100. if (!parser) throw new Error(`Unable to create parser for extension ${ext}`)
  101. this._cachedParsers.push({ext: exporter.ext, parser})
  102. this.dispatchEvent({type: 'exporterCreate', exporter, parser})
  103. return parser
  104. }
  105. private _cachedParsers: {parser: IExportParser, ext: string[]}[] = []
  106. private _getParser(ext: string): IExportParser {
  107. return this._cachedParsers.find(e => e.ext.includes(ext))?.parser ?? this._createParser(ext)
  108. }
  109. public async processBeforeExport(obj: IObject3D|IMaterial|ITexture|IRenderTarget, options: ExportFileOptions = {}): Promise<{obj:any, ext:string, typeExt?:string, blob?: BlobExt}|undefined> {
  110. // if (obj.assetExporterProcessed && !options.forceExporterReprocess) return obj //todo;;;
  111. switch (obj.assetType) {
  112. case 'light':
  113. console.error('AssetExporter: light export not implemented')
  114. return undefined
  115. case 'model':
  116. return {obj, ext: 'glb'}
  117. // return {obj, ext: 'gltf'}
  118. case 'material':
  119. return {obj: (obj as IMaterial).toJSON(), ext: (obj as IMaterial).constructor?.TypeSlug || 'json', typeExt: 'json'}
  120. case 'texture':
  121. return options.exportExt ? {obj, ext: options.exportExt} : {obj: (obj as ITexture).toJSON(), ext: 'json'}
  122. case 'renderTarget':
  123. if (obj.isWebGLMultipleRenderTargets) console.error('AssetExporter: WebGLMultipleRenderTargets export not supported')
  124. else if (!obj.renderManager) return {obj, ext: 'exr'}
  125. else {
  126. const blob = obj.renderManager.exportRenderTarget(obj as WebGLRenderTarget,
  127. (options.exportExt || '' !== '') && options.exportExt !== 'auto' ?
  128. options.exportExt === 'exr' ? 'image/x-exr' : 'image/' + options.exportExt : 'auto')
  129. return {
  130. obj, ext: blob.ext, blob,
  131. }
  132. }
  133. break
  134. default:
  135. console.error('AssetExporter: unknown asset type', obj.assetType)
  136. }
  137. return undefined
  138. }
  139. dispose(): void {
  140. // todo
  141. }
  142. }