Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support zipped versions of gltf and glb files #656

Merged
merged 7 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed docs/assets/geometry/LHCb/LHCb.glb
Binary file not shown.
Binary file added docs/assets/geometry/LHCb/LHCb.glb.zip
Binary file not shown.
22 changes: 10 additions & 12 deletions packages/phoenix-event-display/src/event-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,24 +327,22 @@ export class EventDisplay {
}

/**
* Parses and loads a geometry in GLTF (.gltf) format.
* @param input Data of the GLTF (.gltf) file.
* @param name Name given to the geometry. If empty Name will be taken from the geometry itself
* Parses and loads a geometry in GLTF (.gltf or .glb) format.
* also supports zip files of the above
* @param file Geometry file in GLTF (.gltf or .glb) format.
* @returns Promise for loading the geometry.
*/
public async parseGLTFGeometry(
input: string | ArrayBuffer,
name: string,
): Promise<void> {
this.loadingManager.addLoadableItem(`parse_gltf_${name}`);
public async parseGLTFGeometry(file: File): Promise<void> {
const filename = file.name.split('/').pop();
this.loadingManager.addLoadableItem(`parse_gltf_${filename}`);

const allGeometriesUIParameters =
await this.graphicsLibrary.parseGLTFGeometry(input, name);
for (const { object } of allGeometriesUIParameters) {
this.ui.addGeometry(object);
await this.graphicsLibrary.parseGLTFGeometry(file);
for (const { object, menuNodeName } of allGeometriesUIParameters) {
this.ui.addGeometry(object, menuNodeName);
}

this.loadingManager.itemLoaded(`parse_gltf_${name}`);
this.loadingManager.itemLoaded(`parse_gltf_${filename}`);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { GeometryUIParameters } from '../../lib/types/geometry-ui-parameters';
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils';
import JSZip from 'jszip';

/**
* Manager for managing event display's import related functionality.
Expand Down Expand Up @@ -209,8 +210,76 @@ export class ImportManager {
}

/**
* Loads a GLTF (.gltf) scene(s)/geometry from the given URL.
* @param sceneUrl URL to the GLTF (.gltf) file.
* 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
* @returns Promise for loading the geometry.
*/
private zipHandlingWrapper(
file: File | string,
callback: (
fileContent: ArrayBuffer,
path: string,
name: string,
) => Promise<GeometryUIParameters[]>,
): Promise<GeometryUIParameters[]> {
if (typeof file != 'string') {
file = file.name;
}
const path = file.substr(0, file.lastIndexOf('/'));
return new Promise<GeometryUIParameters[]>((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<GeometryUIParameters[]>[] = [];
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) {
// this.eventDisplay
// .getInfoLogger()
// .add('Could not read zip file', 'Error');
EdwardMoyse marked this conversation as resolved.
Show resolved Hide resolved
reject(error);
}
} else {
callback(data, path, file.split('.')[0]).then(
(geo) => {
resolve(geo);
},
(error) => {
reject(error);
},
);
}
});
});
});
}

/**
* Loads a GLTF (.gltf,.glb) scene(s)/geometry from the given URL.
* also support zipped versions of the files
* @param sceneUrl URL to the GLTF (.gltf/.glb or a zip with such file(s)) file.
* @param name Name of the loaded scene/geometry if a single scene is present, ignored if several scenes are present.
* @param menuNodeName Path to the node in Phoenix menu to add the geometry to. Use `>` as a separator.
* @param scale Scale of the geometry.
Expand All @@ -223,6 +292,39 @@ export class ImportManager {
menuNodeName: string,
scale: number,
initiallyVisible: boolean,
): Promise<GeometryUIParameters[]> {
return this.zipHandlingWrapper(
sceneUrl,
(data: ArrayBuffer, path: string, ignoredName: string) => {
return this.loadGLTFGeometryInternal(
data,
path,
name,
menuNodeName,
scale,
initiallyVisible,
);
},
);
}

/**
* Loads a GLTF (.gltf) scene(s)/geometry from the given ArrayBuffer.
* @param sceneData ArrayBuffer containing the geometry file's content (gltf or glb data)
* @param path The base path from which to find subsequent glTF resources such as textures and .bin data files
* @param name Name of the loaded scene/geometry if a single scene is present, ignored if several scenes are present.
* @param menuNodeName Path to the node in Phoenix menu to add the geometry to. Use `>` as a separator.
* @param scale Scale of the geometry.
* @param initiallyVisible Whether the geometry is initially visible or not.
* @returns Promise for loading the geometry.
*/
private loadGLTFGeometryInternal(
sceneData: ArrayBuffer,
path: string,
name: string,
menuNodeName: string,
scale: number,
initiallyVisible: boolean,
): Promise<GeometryUIParameters[]> {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
Expand All @@ -232,8 +334,9 @@ export class ImportManager {
loader.setDRACOLoader(dracoLoader);

return new Promise<GeometryUIParameters[]>((resolve, reject) => {
loader.load(
sceneUrl,
loader.parse(
sceneData,
path,
(gltf) => {
const allGeometries: GeometryUIParameters[] = [];

Expand Down Expand Up @@ -293,25 +396,36 @@ export class ImportManager {
menuNodeName: menuNodeName ?? sceneName.menuNodeName,
});
}

resolve(allGeometries);
},
undefined,
(error) => {
reject(error);
},
);
});
}

/**
* Parses and loads a geometry in GLTF (.gltf) format.
* @param geometry Geometry in GLTF (.gltf) format.
/** Parses and loads a geometry in GLTF (.gltf,.glb) format.
* Also supports zip versions of those
* @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<GeometryUIParameters[]> {
return this.zipHandlingWrapper(
fileName,
this.parseGLTFGeometryFromArrayBuffer,
);
}

/** Parses and loads a geometry in GLTF (.gltf) format.
* @param geometry ArrayBuffer containing the geometry file's content (gltf or glb data)
* @param path The base path from which to find subsequent glTF resources such as textures and .bin data files
* @param name Name given to the geometry.
* @returns Promise for loading the geometry.
*/
public parseGLTFGeometry(
geometry: string | ArrayBuffer,
private parseGLTFGeometryFromArrayBuffer(
geometry: ArrayBuffer,
path: string,
name: string,
): Promise<GeometryUIParameters[]> {
const loader = new GLTFLoader();
Expand All @@ -323,17 +437,18 @@ export class ImportManager {
return new Promise<GeometryUIParameters[]>((resolve, reject) => {
loader.parse(
geometry,
'',
path,
(gltf) => {
const allGeometriesUIParameters: GeometryUIParameters[] = [];

for (const scene of gltf.scenes) {
scene.visible = scene.userData.visible;
const sceneName = this.processGLTFSceneName(scene.name);
this.processGeometry(scene, name ?? sceneName.name);
this.processGeometry(scene, sceneName.name ?? name);

allGeometriesUIParameters.push({
object: scene,
menuNodeName: sceneName.menuNodeName,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -829,21 +829,18 @@ export class ThreeManager {
}

/**
* Parses and loads a geometry in GLTF (.gltf) format.
* @param geometry Geometry in GLTF (.gltf) format.
* @param name Name given to the geometry.
* Parses and loads a geometry in GLTF (.gltf or .glb) format.
* also supports zip files of the above
* @param file Geometry file in GLTF (.gltf or .glb) format.
* @returns Promise for loading the geometry.
*/
public async parseGLTFGeometry(
geometry: any,
name: string,
): Promise<GeometryUIParameters[]> {
public async parseGLTFGeometry(file: File): Promise<GeometryUIParameters[]> {
const allGeometriesUIParameters =
await this.importManager.parseGLTFGeometry(geometry, name);
await this.importManager.parseGLTFGeometry(file.name);

for (const { object } of allGeometriesUIParameters) {
this.sceneManager.getGeometries().add(object);
this.infoLogger.add(name, 'Parsed GLTF geometry');
this.infoLogger.add(file.name, 'Parsed GLTF geometry');
}

return allGeometriesUIParameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class LHCbComponent implements OnInit {

try {
this.eventDisplay.loadGLTFGeometry(
'assets/geometry/LHCb/LHCb.glb',
'assets/geometry/LHCb/LHCb.glb.zip',
undefined, // name, ignored when empty
undefined, // menuNodeName
1, // scale
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ <h1 mat-dialog-title>Import and export</h1>
type="file"
tabindex="-1"
class="file-input"
accept=".gltf"
accept=".gltf,.glb,.gltf.zip,.glb.zip"
name="image"
(change)="handleGLTFInput($event.target.files)"
/>
<button class="file-input-button" (click)="gltfFileInput.click()">
<img src="assets/icons/gltf.svg" alt="" /> Load .gltf
<img src="assets/icons/gltf.svg" alt="" /> Load .gltf/.glb
</button>

<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ export class IOOptionsDialogComponent implements OnInit {
}

handleGLTFInput(files: FileList) {
const callback = (content: any, name: string) => {
this.eventDisplay.parseGLTFGeometry(content, name);
const callback = (file: File) => {
this.eventDisplay.parseGLTFGeometry(file);
};
this.handleFileInput(files[0], 'gltf', callback);
if (this.isFileOfExtension(files[0].name, 'gltf,glb,gltf.zip,glb.zip')) {
callback(files[0]);
}
}

handlePhoenixInput(files: FileList) {
Expand All @@ -170,7 +172,7 @@ export class IOOptionsDialogComponent implements OnInit {
}

async handleRootJSONInput(files: FileList) {
if (!this.isFileOfExtension(files[0], 'gz')) {
if (!this.isFileOfExtension(files[0].name, 'gz')) {
return;
}

Expand All @@ -193,7 +195,7 @@ export class IOOptionsDialogComponent implements OnInit {
}

async handleZipEventDataInput(files: FileList) {
if (!this.isFileOfExtension(files[0], 'zip')) {
if (!this.isFileOfExtension(files[0].name, 'zip')) {
return;
}

Expand Down Expand Up @@ -236,23 +238,22 @@ export class IOOptionsDialogComponent implements OnInit {

handleFileInput(
file: File,
extension: string,
extensions: string,
callback: (result: string, fileName?: string) => void,
) {
const reader = new FileReader();

if (this.isFileOfExtension(file, extension)) {
if (this.isFileOfExtension(file.name, extensions)) {
reader.onload = () => {
callback(reader.result.toString(), file.name.split('.')[0]);
};
reader.readAsText(file);
}

this.onClose();
}

private isFileOfExtension(file: File, extension: string): boolean {
if (file.name.split('.').pop() === extension) {
private isFileOfExtension(fileName: string, extensions: string): boolean {
if (extensions.split(',').includes(fileName.split('.', 2).pop())) {
return true;
}

Expand Down
Loading