src/managers/three-manager/import-manager.ts
Manager for managing event display's import related functionality.
Properties |
|
Methods |
|
constructor(clipPlanes: Plane[], EVENT_DATA_ID: string, GEOMETRIES_ID: string)
|
Constructor for the import manager. |
Private clipPlanes |
Type : Plane[]
|
Planes for clipping geometry. |
Private EVENT_DATA_ID |
Type : string
|
Object group ID containing event data. |
Private GEOMETRIES_ID |
Type : string
|
Object group ID containing detector geometries. |
Private getObjectSize | ||||||||
getObjectSize(object: Mesh)
|
||||||||
Get the size of object.
Parameters :
Returns :
string
The size (vector) of object as a string. |
Public loadGLTFGeometry | ||||||||||||||||||||||||
loadGLTFGeometry(sceneUrl: string, name: string, menuNodeName: string, scale: number, initiallyVisible: boolean)
|
||||||||||||||||||||||||
Loads a GLTF (.gltf,.glb) scene(s)/geometry from the given URL. also support zipped versions of the files
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
Private loadGLTFGeometryInternal | ||||||||||||||||||||||||||||
loadGLTFGeometryInternal(sceneData: ArrayBuffer, path: string, name: string, menuNodeName: string, scale: number, initiallyVisible: boolean)
|
||||||||||||||||||||||||||||
Loads a GLTF (.gltf) scene(s)/geometry from the given ArrayBuffer.
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
Public loadJSONGeometry | ||||||||||||||||||||
loadJSONGeometry(json: string | literal type, name: string, scale?: number, doubleSided?: boolean)
|
||||||||||||||||||||
Loads geometries from JSON.
Parameters :
Returns :
Promise<GeometryUIParameters>
Promise for loading the geometry. |
Public loadOBJGeometry | ||||||||||||||||||||||||
loadOBJGeometry(filename: string, name: string, color: any, doubleSided: boolean, setFlat: boolean)
|
||||||||||||||||||||||||
Loads an OBJ (.obj) geometry from the given filename.
Parameters :
Returns :
Promise<GeometryUIParameters>
Promise for loading the geometry. |
Public parseGLTFGeometry | ||||||
parseGLTFGeometry(file: File)
|
||||||
Parses and loads a geometry in GLTF (.gltf,.glb) format. Also supports zip versions of those
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
Private parseGLTFGeometryFromArrayBuffer | ||||||||||||||||
parseGLTFGeometryFromArrayBuffer(geometry: ArrayBuffer, path: string, name: string)
|
||||||||||||||||
Parses and loads a geometry in GLTF (.gltf) format.
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
Public parseOBJGeometry | ||||||||||||
parseOBJGeometry(geometry: string, name: string)
|
||||||||||||
Parses and loads a geometry in OBJ (.obj) format.
Parameters :
Returns :
Object3D
The processed object. |
Public parsePhnxScene | ||||||||||||
parsePhnxScene(scene: any, callback: (geometries: Object3D,eventData: Object3D) => void)
|
||||||||||||
Parses and loads a scene in Phoenix (.phnx) format.
Parameters :
Returns :
Promise<void>
Promise for loading the scene. |
Private processGeometry | ||||||||||||||||||||
processGeometry(geometry: Object3D, name: string, scale?: number, doubleSided?: boolean)
|
||||||||||||||||||||
Process the geometry by setting up material and clipping attributes.
Parameters :
Returns :
void
|
Private processGLTFSceneName | ||||||||||||
processGLTFSceneName(sceneName?: string, menuNodeName?: string)
|
||||||||||||
Get geometry name and menuNodeName from GLTF scene name.
Parameters :
Returns :
{ name: any; menuNodeName: any; }
Geometry name and menuNodeName if present in scene name. |
Private processOBJ | ||||||||||||||||||||||||
processOBJ(object: Object3D, name: string, color: any, doubleSided: boolean, setFlat: boolean)
|
||||||||||||||||||||||||
Process the geometry object being loaded from OBJ (.obj) format.
Parameters :
Returns :
Object3D
The processed object. |
Private setObjFlat | ||||||||||||||||||||
setObjFlat(object3d: Object3D, color: any, doubleSided: boolean, setFlat: boolean)
|
||||||||||||||||||||
Process the 3D object and flatten it.
Parameters :
Returns :
Object3D
The processed object. |
Private zipHandlingFileWrapper | ||||||||||||
zipHandlingFileWrapper(file: File, callback: (fileContent: ArrayBuffer,path: string,name: string) => void)
|
||||||||||||
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
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
Private zipHandlingInternal | ||||||||||||||||||||||||||||
zipHandlingInternal(path: string, filename: string, data: ArrayBuffer, callback: (fileContent: ArrayBuffer,path: string,name: string) => void, resolve: any, reject: any)
|
||||||||||||||||||||||||||||
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
Parameters :
Returns :
void
|
Private zipHandlingURLWrapper | ||||||||||||
zipHandlingURLWrapper(file: string, callback: (fileContent: ArrayBuffer,path: string,name: string) => void)
|
||||||||||||
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
Parameters :
Returns :
Promise<GeometryUIParameters[]>
Promise for loading the geometry. |
import {
DoubleSide,
Mesh,
LineSegments,
LineBasicMaterial,
MeshPhongMaterial,
Object3D,
Plane,
Material,
ObjectLoader,
Color,
FrontSide,
Vector3,
Matrix4,
REVISION,
} from 'three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import type { GeometryUIParameters } from '../../lib/types/geometry-ui-parameters';
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import JSZip from 'jszip';
/**
* Manager for managing event display's import related functionality.
*/
export class ImportManager {
/** Planes for clipping geometry. */
private clipPlanes: Plane[];
/** Object group ID containing event data. */
private EVENT_DATA_ID: string;
/** Object group ID containing detector geometries. */
private GEOMETRIES_ID: string;
/**
* Constructor for the import manager.
* @param clipPlanes Planes for clipping geometry.
* @param EVENT_DATA_ID Object group ID containing event data.
* @param GEOMETRIES_ID Object group ID containing detector geometries.
*/
constructor(
clipPlanes: Plane[],
EVENT_DATA_ID: string,
GEOMETRIES_ID: string,
) {
this.clipPlanes = clipPlanes;
this.EVENT_DATA_ID = EVENT_DATA_ID;
this.GEOMETRIES_ID = GEOMETRIES_ID;
}
/**
* Loads an OBJ (.obj) geometry from the given filename.
* @param filename Path to the geometry.
* @param name Name given to the geometry.
* @param color Color to initialize the geometry.
* @param doubleSided Renders both sides of the material.
* @param setFlat Whether object should be flat-shaded or not.
* @returns Promise for loading the geometry.
*/
public loadOBJGeometry(
filename: string,
name: string,
color: any,
doubleSided: boolean,
setFlat: boolean,
): Promise<GeometryUIParameters> {
color = color ?? 0x41a6f4;
const objLoader = new OBJLoader();
return new Promise<GeometryUIParameters>((resolve, reject) => {
objLoader.load(
filename,
(object) => {
const processedObject = this.processOBJ(
object,
name,
color,
doubleSided,
setFlat,
);
resolve({ object: processedObject });
},
() => {},
(error) => {
reject(error);
},
);
});
}
/**
* Parses and loads a geometry in OBJ (.obj) format.
* @param geometry Geometry in OBJ (.obj) format.
* @param name Name given to the geometry.
* @returns The processed object.
*/
public parseOBJGeometry(geometry: string, name: string): Object3D {
const objLoader = new OBJLoader();
const object = objLoader.parse(geometry);
return this.processOBJ(object, name, 0x41a6f4, false, false);
}
/**
* Process the geometry object being loaded from OBJ (.obj) format.
* @param object 3D object.
* @param name Name of the object.
* @param color Color of the object.
* @param doubleSided Renders both sides of the material.
* @param setFlat Whether object should be flat-shaded or not.
* @returns The processed object.
*/
private processOBJ(
object: Object3D,
name: string,
color: any,
doubleSided: boolean,
setFlat: boolean,
): Object3D {
object.name = name;
object.userData = { name };
return this.setObjFlat(object, color, doubleSided, setFlat);
}
/**
* Process the 3D object and flatten it.
* @param object3d Group of geometries that make up the object.
* @param color Color of the object.
* @param doubleSided Renders both sides of the material.
* @param setFlat Whether object should be flat-shaded or not.
* @returns The processed object.
*/
private setObjFlat(
object3d: Object3D,
color: any,
doubleSided: boolean,
setFlat: boolean,
): Object3D {
const material2 = new MeshPhongMaterial({
color: color,
shininess: 0,
wireframe: false,
clippingPlanes: this.clipPlanes,
clipIntersection: true,
clipShadows: false,
side: doubleSided ? DoubleSide : FrontSide,
flatShading: setFlat,
});
object3d.traverse((child: Object3D) => {
if (child instanceof Mesh) {
child.name = object3d.name;
child.userData = object3d.userData;
child.userData.size = this.getObjectSize(child);
// Use the new material
if (child.material instanceof Material) {
child.material.dispose();
child.material = material2;
}
// enable casting shadows
child.castShadow = false;
child.receiveShadow = false;
} else {
if (
child instanceof LineSegments &&
child.material instanceof LineBasicMaterial
) {
(child.material.color as Color).set(color);
}
}
});
return object3d;
}
/**
* Parses and loads a scene in Phoenix (.phnx) format.
* @param scene Geometry in Phoenix (.phnx) format.
* @param callback Callback called after the scene is loaded.
* @returns Promise for loading the scene.
*/
public parsePhnxScene(
scene: any,
callback: (geometries?: Object3D, eventData?: Object3D) => void,
): Promise<void> {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
`https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0/examples/jsm/libs/draco/`,
);
loader.setDRACOLoader(dracoLoader);
const sceneString = JSON.stringify(scene, null, 2);
return new Promise<void>((resolve, reject) => {
loader.parse(
sceneString,
'',
(gltf) => {
const eventData = gltf.scene.getObjectByName(this.EVENT_DATA_ID);
const geometries = gltf.scene.getObjectByName(this.GEOMETRIES_ID);
callback(eventData, geometries);
resolve();
},
(error) => {
reject(error);
},
);
});
}
/**
* 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<GeometryUIParameters[]>,
resolve: any,
reject: any,
) {
if (filename.split('.').pop() == 'zip') {
JSZip.loadAsync(data).then((archive) => {
const promises: Promise<any>[] = [];
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 original method
* @returns Promise for loading the geometry.
*/
private zipHandlingFileWrapper(
file: File,
callback: (
fileContent: ArrayBuffer,
path: string,
name: string,
) => Promise<GeometryUIParameters[]>,
): Promise<GeometryUIParameters[]> {
return new Promise<GeometryUIParameters[]>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
this.zipHandlingInternal(
'',
file.name,
reader.result as ArrayBuffer,
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<GeometryUIParameters[]>,
): Promise<GeometryUIParameters[]> {
return new Promise<GeometryUIParameters[]>((resolve, reject) => {
fetch(file).then((response) => {
return response.arrayBuffer().then((data) => {
this.zipHandlingInternal(
file.substr(0, file.lastIndexOf('/')),
file,
data,
callback,
resolve,
reject,
);
});
});
});
}
/**
* 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.
* @param initiallyVisible Whether the geometry is initially visible or not.
* @returns Promise for loading the geometry.
*/
public loadGLTFGeometry(
sceneUrl: string,
name: string,
menuNodeName: string,
scale: number,
initiallyVisible: boolean,
): Promise<GeometryUIParameters[]> {
return this.zipHandlingURLWrapper(
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();
dracoLoader.setDecoderPath(
`https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0/examples/jsm/libs/draco/`,
);
loader.setDRACOLoader(dracoLoader);
return new Promise<GeometryUIParameters[]>((resolve, reject) => {
loader.parse(
sceneData,
path,
(gltf) => {
const allGeometries: GeometryUIParameters[] = [];
for (const scene of gltf.scenes) {
scene.visible = scene.userData.visible ?? initiallyVisible;
const sceneName = this.processGLTFSceneName(
scene.name,
menuNodeName,
);
const materials: {
[key: string]: {
material: Material;
geoms: any[];
renderOrder: number;
};
} = {};
const findMeshes = (
node: Object3D,
parentMatrix: Matrix4,
depth: number,
) => {
const mat = parentMatrix.clone().multiply(node.matrix);
if (node instanceof Mesh) {
const key = ((node as Mesh).material as any).id; // ts don't recognize material and prevent compilation...
if (!materials[key])
materials[key] = {
material: (node as Mesh).material as Material, // Can be Material[], but not sure this is ever still used.
geoms: [],
renderOrder: -depth,
};
materials[key].geoms.push(
(node as Mesh).geometry.clone().applyMatrix4(mat),
);
}
for (const obj of node.children) {
findMeshes(obj, mat, depth + 1);
}
};
findMeshes(scene, new Matrix4(), 0);
// Improve renderorder for transparent materials
scene.remove(...scene.children);
for (const val of Object.values(materials)) {
const mesh = new Mesh(
BufferGeometryUtils.mergeGeometries((val as any).geoms),
(val as any).material,
);
mesh.renderOrder = (val as any).renderOrder;
scene.add(mesh);
}
this.processGeometry(
scene,
name ?? sceneName?.name,
scale,
true, // doublesided
);
allGeometries.push({
object: scene,
menuNodeName: menuNodeName ?? sceneName?.menuNodeName,
});
}
resolve(allGeometries);
},
(error) => {
reject(error);
},
);
});
}
/** 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(file: File): Promise<GeometryUIParameters[]> {
return this.zipHandlingFileWrapper(
file,
(data: ArrayBuffer, path: string, name: string) => {
return this.parseGLTFGeometryFromArrayBuffer(data, path, name);
},
);
}
/** 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.
*/
private parseGLTFGeometryFromArrayBuffer(
geometry: ArrayBuffer,
path: string,
name: string,
): Promise<GeometryUIParameters[]> {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
`https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0/examples/jsm/libs/draco/`,
);
loader.setDRACOLoader(dracoLoader);
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;
console.log('Dealing with scene ', scene.name);
const sceneName = this.processGLTFSceneName(scene.name);
this.processGeometry(scene, sceneName?.name ?? name);
allGeometriesUIParameters.push({
object: scene,
menuNodeName: sceneName?.menuNodeName,
});
}
resolve(allGeometriesUIParameters);
},
(error) => {
reject(error);
},
);
});
}
/**
* Get geometry name and menuNodeName from GLTF scene name.
* @param sceneName GLTF scene name.
* @param menuNodeName Path to the node in Phoenix menu to add the geometry to. Use `>` as a separator.
* @returns Geometry name and menuNodeName if present in scene name.
*/
private processGLTFSceneName(sceneName?: string, menuNodeName?: string) {
if (sceneName) {
const nodes = sceneName.split('_>_');
menuNodeName && nodes.unshift(menuNodeName); // eslint-disable-line
const fullNodeName = nodes.join(' > ');
nodes.pop();
const menuName = nodes.join(' > ');
return { name: fullNodeName, menuNodeName: menuName };
}
}
/**
* Loads geometries from JSON.
* @param json JSON or URL to JSON file of the geometry.
* @param name Name of the geometry or group of geometries.
* @param scale Scale of the geometry.
* @param doubleSided Renders both sides of the material.
* @returns Promise for loading the geometry.
*/
public loadJSONGeometry(
json: string | { [key: string]: any },
name: string,
scale?: number,
doubleSided?: boolean,
): Promise<GeometryUIParameters> {
const loader = new ObjectLoader();
switch (typeof json) {
case 'string':
return new Promise<GeometryUIParameters>((resolve, reject) => {
loader.load(
json,
(object: Object3D) => {
this.processGeometry(object, name, scale, doubleSided);
resolve({ object });
},
undefined,
(error) => {
reject(error);
},
);
});
case 'object':
return new Promise<GeometryUIParameters>((resolve) => {
const object = loader.parse(json);
this.processGeometry(object, name, scale, doubleSided);
resolve({ object });
});
}
}
/**
* Process the geometry by setting up material and clipping attributes.
* @param geometry Geometry to be processed.
* @param name Name of the geometry.
* @param scale Scale of the geometry.
* @param doubleSided Renders both sides of the material.
* @param transparent Whether the transparent property of geometry is true or false. Default `false`.
*/
private processGeometry(
geometry: Object3D,
name: string,
scale?: number,
doubleSided?: boolean,
) {
geometry.name = name;
// Set a custom scale if provided
if (scale) {
geometry.scale.setScalar(scale);
}
geometry.traverse((child) => {
if (child instanceof Mesh) {
child.name = child.userData.name = name;
child.userData.size = this.getObjectSize(child);
if (child.material instanceof Material) {
const mat = child.material as Material;
const color =
'color' in mat ? (mat.color as Color).getHex() : 0x2fd691;
const side = doubleSided ? DoubleSide : child.material['side'];
// Disposing of the default material
child.material.dispose();
// Should tranparency be used?
let isTransparent = false;
if (geometry.userData.opacity) {
isTransparent = true;
}
// Changing to a material with 0 shininess
child.material = new MeshPhongMaterial({
color,
shininess: 0,
side: side,
transparent: isTransparent,
opacity: geometry.userData.opacity ?? 1,
});
// Setting up the clipping planes
child.material.clippingPlanes = this.clipPlanes;
child.material.clipIntersection = true;
child.material.clipShadows = false;
}
}
});
}
/**
* Get the size of object.
* @param object Object to get the size of.
* @returns The size (vector) of object as a string.
*/
private getObjectSize(object: Mesh): string {
const size = new Vector3();
object.geometry.computeBoundingBox();
object.geometry?.boundingBox?.getSize(size);
return JSON.stringify(size, null, 2);
}
}