threepipe
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

AssetExporter.ts 5.3KB

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