From c181ca8922b81699c3add5a7f05cea8f91f10e4d Mon Sep 17 00:00:00 2001 From: Sebastien Ponce Date: Thu, 20 Jun 2024 17:58:01 +0200 Subject: [PATCH 1/2] Fixed support for gltf and glb zip files --- .../src/event-display.ts | 6 +- .../managers/three-manager/import-manager.ts | 160 ++++++++++++------ .../src/managers/three-manager/index.ts | 2 +- .../io-options-dialog.component.ts | 14 +- 4 files changed, 124 insertions(+), 58 deletions(-) diff --git a/packages/phoenix-event-display/src/event-display.ts b/packages/phoenix-event-display/src/event-display.ts index d42ea11c..2b05ab64 100644 --- a/packages/phoenix-event-display/src/event-display.ts +++ b/packages/phoenix-event-display/src/event-display.ts @@ -333,8 +333,8 @@ export class EventDisplay { * @returns Promise for loading the geometry. */ public async parseGLTFGeometry(file: File): Promise { - const filename = file.name.split('/').pop(); - this.loadingManager.addLoadableItem(`parse_gltf_${filename}`); + name = file.name.split('/').pop(); + this.loadingManager.addLoadableItem(`parse_gltf_${name}`); const allGeometriesUIParameters = await this.graphicsLibrary.parseGLTFGeometry(file); @@ -342,7 +342,7 @@ export class EventDisplay { this.ui.addGeometry(object, menuNodeName); } - this.loadingManager.itemLoaded(`parse_gltf_${filename}`); + this.loadingManager.itemLoaded(`parse_gltf_${name}`); } /** diff --git a/packages/phoenix-event-display/src/managers/three-manager/import-manager.ts b/packages/phoenix-event-display/src/managers/three-manager/import-manager.ts index 1c3c8e96..afa2fb03 100644 --- a/packages/phoenix-event-display/src/managers/three-manager/import-manager.ts +++ b/packages/phoenix-event-display/src/managers/three-manager/import-manager.ts @@ -209,66 +209,125 @@ export class ImportManager { }); } + /** + * handles some file content and loads a Geometry contained.. + * It deals with zip file cases and then + * calls the given method on each file found + * @param path path of the original file + * @param filename name of the original file + * @param data content of the original file + * @param callback the method to be called on each file content + * @param resolve the method to be called on success + * @param reject the method to be called on failure + */ + private zipHandlingInternal( + path: string, + filename: string, + data: ArrayBuffer, + callback: ( + fileContent: ArrayBuffer, + path: string, + name: string, + ) => Promise, + resolve: any, + reject: any, + ) { + if (filename.split('.').pop() == 'zip') { + JSZip.loadAsync(data).then((archive) => { + const promises: Promise[] = []; + for (const filePath in archive.files) { + promises.push( + archive + .file(filePath) + .async('arraybuffer') + .then((fileData) => { + return callback(fileData, path, filePath.split('.')[0]); + }), + ); + } + let allGeometriesUIParameters: GeometryUIParameters[] = []; + Promise.all(promises).then((geos) => { + geos.forEach((geo) => { + allGeometriesUIParameters = allGeometriesUIParameters.concat(geo); + }); + resolve(allGeometriesUIParameters); + }); + }); + } else { + callback(data, path, filename.split('.')[0]).then( + (geo) => { + resolve(geo); + }, + (error) => { + reject(error); + }, + ); + } + } + /** * Wraps a method taking a file and returning a Promise for * loading a Geometry. It deals with zip file cases and then * calls the original method on each file found * @param file the original file - * @param callback the orignal mathod + * @param callback the original method * @returns Promise for loading the geometry. */ - private zipHandlingWrapper( - file: File | string, + private zipHandlingFileWrapper( + file: File, + callback: ( + fileContent: ArrayBuffer, + path: string, + name: string, + ) => Promise, + ): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = () => { + this.eventDisplay.getInfoLogger().add('Could not read file', 'Error'); + reject(error); + }; + reader.onload = () => { + this.zipHandlingInternal( + '', + file.name, + reader.result, + callback, + resolve, + reject, + ); + }; + reader.readAsArrayBuffer(file); + }); + } + + /** + * Wraps a method taking a URL and returning a Promise for + * loading a Geometry. It deals with zip file cases and then + * calls the original method on each file found + * @param file the original file + * @param callback the original method + * @returns Promise for loading the geometry. + */ + private zipHandlingURLWrapper( + file: string, callback: ( fileContent: ArrayBuffer, path: string, name: string, ) => Promise, ): Promise { - if (typeof file != 'string') { - file = file.name; - } - const path = file.substr(0, file.lastIndexOf('/')); return new Promise((resolve, reject) => { fetch(file).then((response) => { return response.arrayBuffer().then((data) => { - if (file.split('.').pop() == 'zip') { - try { - JSZip.loadAsync(data).then((archive) => { - const promises: Promise[] = []; - for (const filePath in archive.files) { - promises.push( - archive - .file(filePath) - .async('arraybuffer') - .then((fileData) => { - return callback(fileData, path, filePath.split('.')[0]); - }), - ); - } - let allGeometriesUIParameters: GeometryUIParameters[] = []; - Promise.all(promises).then((geos) => { - geos.forEach((geo) => { - allGeometriesUIParameters = - allGeometriesUIParameters.concat(geo); - }); - resolve(allGeometriesUIParameters); - }); - }); - } catch (error) { - console.warn('Could not read zip file', 'Error'); - reject(error); - } - } else { - callback(data, path, file.split('.')[0]).then( - (geo) => { - resolve(geo); - }, - (error) => { - reject(error); - }, - ); - } + this.zipHandlingInternal( + file.substr(0, file.lastIndexOf('/')), + file, + data, + callback, + resolve, + reject, + ); }); }); }); @@ -291,7 +350,7 @@ export class ImportManager { scale: number, initiallyVisible: boolean, ): Promise { - return this.zipHandlingWrapper( + return this.zipHandlingURLWrapper( sceneUrl, (data: ArrayBuffer, path: string, ignoredName: string) => { return this.loadGLTFGeometryInternal( @@ -408,10 +467,12 @@ export class ImportManager { * @param fileName of the geometry file (.gltf,.glb or a zip with such file(s)) * @returns Promise for loading the geometry. */ - public parseGLTFGeometry(fileName: string): Promise { - return this.zipHandlingWrapper( - fileName, - this.parseGLTFGeometryFromArrayBuffer, + public parseGLTFGeometry(file: File): Promise { + return this.zipHandlingFileWrapper( + file, + (data: ArrayBuffer, path: string, name: string) => { + return this.parseGLTFGeometryFromArrayBuffer(data, path, name); + }, ); } @@ -441,6 +502,7 @@ export class ImportManager { for (const scene of gltf.scenes) { scene.visible = scene.userData.visible; + console.log('Dealing with scene ', scene.name); const sceneName = this.processGLTFSceneName(scene.name); this.processGeometry(scene, sceneName.name ?? name); diff --git a/packages/phoenix-event-display/src/managers/three-manager/index.ts b/packages/phoenix-event-display/src/managers/three-manager/index.ts index 4b377893..f2f0f198 100644 --- a/packages/phoenix-event-display/src/managers/three-manager/index.ts +++ b/packages/phoenix-event-display/src/managers/three-manager/index.ts @@ -836,7 +836,7 @@ export class ThreeManager { */ public async parseGLTFGeometry(file: File): Promise { const allGeometriesUIParameters = - await this.importManager.parseGLTFGeometry(file.name); + await this.importManager.parseGLTFGeometry(file); for (const { object } of allGeometriesUIParameters) { this.sceneManager.getGeometries().add(object); diff --git a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts index 2485bdc6..ac980b26 100644 --- a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts +++ b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts @@ -144,10 +144,12 @@ export class IOOptionsDialogComponent implements OnInit { } handleGLTFInput(files: FileList) { - const callback = (content: any) => { - this.eventDisplay.parseGLTFGeometry(content); + const callback = (file: File) => { + this.eventDisplay.parseGLTFGeometry(file); }; - this.handleFileInput(files[0], 'gltf,glb,gltf.zip,glb.zip', callback); + if (this.isFileOfExtension(files[0].name, 'gltf,glb,gltf.zip,glb.zip')) { + callback(files[0]); + } } handlePhoenixInput(files: FileList) { @@ -193,7 +195,7 @@ export class IOOptionsDialogComponent implements OnInit { } async handleZipEventDataInput(files: FileList) { - if (!this.isFileOfExtension(files[0].name, 'zip')) { + if (!this.isFileOfExtension(files[0].name, 'zip,json.zip')) { return; } @@ -251,7 +253,9 @@ export class IOOptionsDialogComponent implements OnInit { } private isFileOfExtension(fileName: string, extensions: string): boolean { - if (extensions.split(',').includes(fileName.split('.', 2).pop())) { + if ( + extensions.split(',').includes(fileName.slice(fileName.indexOf('.') + 1)) + ) { return true; } From 29cac60b7f1c2ae2d90ed3e78214f3ed4c1a0ee5 Mon Sep 17 00:00:00 2001 From: Edward Moyse Date: Tue, 3 Sep 2024 14:28:29 +0200 Subject: [PATCH 2/2] This test fails becuase handleFileInput is no longer called by handleGLTFInput --- .../io-options-dialog.component.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts index cad31c27..0c28407f 100644 --- a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts +++ b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.test.ts @@ -75,6 +75,15 @@ describe('IoOptionsDialogComponent', () => { expect(mockDialogRef.close).toHaveBeenCalled(); }); + it('should handle glTF file input', () => { + const files = mockFileList([ + new File(['{}'], 'testfile.gltf', { + type: 'application/json', + }), + ]); + component.handleGLTFInput(files); + }); + describe('handleFileInput', () => { beforeEach(() => { jest.spyOn(component, 'handleFileInput').mockImplementation(() => {}); @@ -135,15 +144,6 @@ describe('IoOptionsDialogComponent', () => { component.handleSceneInput(files); }); - it('should handle glTF file input', () => { - const files = mockFileList([ - new File(['{}'], 'testfile.gltf', { - type: 'application/json', - }), - ]); - component.handleGLTFInput(files); - }); - it('should handle phoenix file input', () => { const files = mockFileList([ new File(['{}'], 'testfile.phnx', {