Warning, /firebird/firebird-ng/src/app/workers/geometry-loader.worker.ts is written in an unsupported language. File is not indexed.
0001 /// <reference lib="webworker" />
0002
0003 /**
0004 * Web Worker for loading and processing ROOT geometry files.
0005 *
0006 * This worker performs heavy geometry loading operations off the main thread:
0007 * - Fetches ROOT files via jsroot
0008 * - Parses TGeoManager
0009 * - Prunes and processes geometry
0010 * - Builds Three.js geometry
0011 * - Serializes the result for transfer to main thread
0012 *
0013 * Supports cancellation via requestId tracking.
0014 */
0015
0016 import {openFile} from 'jsroot';
0017 import {build} from 'jsroot/geom';
0018 import {
0019 analyzeGeoNodes,
0020 findGeoManager,
0021 getGeoNodesByLevel
0022 } from '../../lib-root-geometry/root-geo-navigation';
0023 import {pruneTopLevelDetectors, RootGeometryProcessor} from '../data-pipelines/root-geometry.processor';
0024
0025 // Message types for communication with main thread
0026 export interface GeometryLoadRequest {
0027 type: 'load';
0028 requestId: string;
0029 url: string;
0030 options: GeometryLoadOptions;
0031 }
0032
0033 export interface GeometryLoadOptions {
0034 cutListName: string; // "central" or other - controls pruning
0035 rootFilterName: string; // "default" or other - controls pre-processing
0036 }
0037
0038 export interface GeometryCancelRequest {
0039 type: 'cancel';
0040 requestId: string;
0041 }
0042
0043 export type WorkerRequest = GeometryLoadRequest | GeometryCancelRequest;
0044
0045 export interface GeometryLoadSuccess {
0046 type: 'success';
0047 requestId: string;
0048 geometryJson: any; // Serialized Object3D via toJSON()
0049 subdetectorInfos: SubdetectorInfo[]; // Metadata about subdetectors
0050 }
0051
0052 export interface SubdetectorInfo {
0053 name: string;
0054 originalName: string;
0055 groupName: string;
0056 }
0057
0058 export interface GeometryLoadError {
0059 type: 'error';
0060 requestId: string;
0061 error: string;
0062 }
0063
0064 export interface GeometryLoadCancelled {
0065 type: 'cancelled';
0066 requestId: string;
0067 }
0068
0069 export interface GeometryLoadProgress {
0070 type: 'progress';
0071 requestId: string;
0072 stage: string;
0073 progress: number; // 0-100
0074 }
0075
0076 export type WorkerResponse = GeometryLoadSuccess | GeometryLoadError | GeometryLoadCancelled | GeometryLoadProgress;
0077
0078 // Detectors to remove when cutListName is "central"
0079 const removeDetectorNames: string[] = [
0080 "Lumi",
0081 "B1",
0082 "B2",
0083 "Q2",
0084 "ForwardOffM",
0085 "Forward",
0086 "Backward",
0087 "Vacuum",
0088 "SweeperMag",
0089 "AnalyzerMag",
0090 "ZDC",
0091 "HcalFarForward",
0092 "InnerTrackingSupport"
0093 ];
0094
0095 // Map detector names to groups (same as in geometry.service.ts)
0096 const groupsByDetName = new Map<string, string>([
0097 ["SolenoidBarrel_assembly_0", "Magnets"],
0098 ["SolenoidEndcapP_1", "Magnets"],
0099 ["SolenoidEndcapN_2", "Magnets"],
0100 ["VertexBarrelSubAssembly_3", "Tracking"],
0101 ["InnerSiTrackerSubAssembly_4", "Tracking"],
0102 ["MiddleSiTrackerSubAssembly_5", "Tracking"],
0103 ["OuterSiTrackerSubAssembly_6", "Tracking"],
0104 ["EndcapMPGDSubAssembly_7", "Tracking"],
0105 ["InnerMPGDBarrelSubAssembly_8", "Tracking"],
0106 ["EndcapTOFSubAssembly_9", "PID"],
0107 ["BarrelTOFSubAssembly_10", "PID"],
0108 ["OuterBarrelMPGDSubAssembly_11", "Tracking"],
0109 ["B0TrackerSubAssembly_12", "Tracking"],
0110 ["InnerTrackerSupport_assembly_13", "Beam pipe and support"],
0111 ["DIRC_14", "PID"],
0112 ["RICHEndcapN_Vol_15", "PID"],
0113 ["DRICH_16", "PID"],
0114 ["EcalEndcapP_17", "Calorimeters"],
0115 ["EcalEndcapPInsert_18", "Calorimeters"],
0116 ["EcalBarrelImaging_19", "Calorimeters"],
0117 ["EcalBarrelScFi_20", "Calorimeters"],
0118 ["EcalEndcapN_21", "Calorimeters"],
0119 ["LFHCAL_env_22", "Calorimeters"],
0120 ["HcalEndcapPInsert_23", "Calorimeters"],
0121 ["HcalBarrel_24", "Calorimeters"],
0122 ["FluxBarrel_env_25", "Beam pipe and support"],
0123 ["FluxEndcapP_26", "Beam pipe and support"],
0124 ["HcalEndcapN_27", "Calorimeters"],
0125 ["FluxEndcapN_28", "Beam pipe and support"],
0126 ["BeamPipe_assembly_29", "Beam pipe and support"],
0127 ["B0PF_BeamlineMagnet_assembly_30", "Magnets"],
0128 ["B0APF_BeamlineMagnet_assembly_31", "Magnets"],
0129 ["Q1APF_BeamlineMagnet_assembly_32", "Magnets"],
0130 ["Q1BPF_BeamlineMagnet_assembly_33", "Magnets"],
0131 ["BeamPipeB0_assembly_38", "Beam pipe and support"],
0132 ["Pipe_cen_to_pos_assembly_39", "Beam pipe and support"],
0133 ["Q0EF_assembly_40", "Magnets"],
0134 ["Q0EF_vac_41", "Magnets"],
0135 ["Q1EF_assembly_42", "Magnets"],
0136 ["Q1EF_vac_43", "Magnets"],
0137 ["B0ECal_44", "Calorimeters"],
0138 ["Pipe_Q1eR_to_B2BeR_assembly_54", "Beam pipe and support"],
0139 ["Magnet_Q1eR_assembly_55", "Magnets"],
0140 ["Magnet_Q2eR_assembly_56", "Magnets"],
0141 ["Magnet_B2AeR_assembly_57", "Magnets"],
0142 ["Magnet_B2BeR_assembly_58", "Magnets"],
0143 ["Magnets_Q3eR_assembly_59", "Magnets"],
0144 ]);
0145
0146 // Track active requests for cancellation
0147 let activeRequestId: string | null = null;
0148 let cancellationRequested = false;
0149 let isProcessing = false; // Prevents concurrent processing
0150
0151 const rootGeometryProcessor = new RootGeometryProcessor();
0152
0153 function stripIdFromName(name: string): string {
0154 return name.replace(/_\d+$/, '');
0155 }
0156
0157 function sendProgress(requestId: string, stage: string, progress: number) {
0158 const response: GeometryLoadProgress = {
0159 type: 'progress',
0160 requestId,
0161 stage,
0162 progress
0163 };
0164 postMessage(response);
0165 }
0166
0167 async function loadGeometry(request: GeometryLoadRequest): Promise<void> {
0168 const {requestId, url, options} = request;
0169
0170 // If already processing, mark for cancellation and wait for it to finish
0171 if (isProcessing) {
0172 console.log(`[GeometryWorker]: Already processing ${activeRequestId}, marking for cancellation`);
0173 cancellationRequested = true;
0174
0175 // Wait for current processing to finish before starting new one
0176 while (isProcessing) {
0177 await new Promise(resolve => setTimeout(resolve, 50));
0178 }
0179 }
0180
0181 isProcessing = true;
0182 activeRequestId = requestId;
0183 cancellationRequested = false;
0184
0185 try {
0186 // Check for cancellation at key points
0187 const checkCancellation = () => {
0188 if (cancellationRequested) {
0189 throw new Error('CANCELLED');
0190 }
0191 };
0192
0193 sendProgress(requestId, 'Opening ROOT file', 10);
0194
0195 console.time('[GeometryWorker]: Open root file');
0196 const file = await openFile(url);
0197 console.timeEnd('[GeometryWorker]: Open root file');
0198
0199 checkCancellation();
0200 sendProgress(requestId, 'Reading geometry', 20);
0201
0202 console.time('[GeometryWorker]: Reading geometry from file');
0203 const rootGeometry = await findGeoManager(file);
0204 console.timeEnd('[GeometryWorker]: Reading geometry from file');
0205
0206 if (!rootGeometry) {
0207 throw new Error('No TGeoManager found in ROOT file');
0208 }
0209
0210 checkCancellation();
0211 sendProgress(requestId, 'Pruning geometry', 30);
0212
0213 // Prune top-level detectors if configured
0214 if (options.cutListName === "central") {
0215 const result = pruneTopLevelDetectors(rootGeometry, removeDetectorNames);
0216 console.log(`[GeometryWorker]: Pruned geometry. Nodes left: ${result.nodes.length}, Removed: ${result.removedNodes.length}`);
0217 }
0218
0219 checkCancellation();
0220 sendProgress(requestId, 'Pre-processing geometry', 40);
0221
0222 // Apply ROOT geometry processing
0223 if (options.rootFilterName === "default") {
0224 console.time('[GeometryWorker]: Root geometry pre-processing');
0225 rootGeometryProcessor.process(rootGeometry);
0226 console.timeEnd('[GeometryWorker]: Root geometry pre-processing');
0227 }
0228
0229 checkCancellation();
0230 sendProgress(requestId, 'Analyzing geometry', 50);
0231
0232 console.log("[GeometryWorker]: Number of tree elements analysis (after root geometry prune):");
0233 analyzeGeoNodes(rootGeometry, 1);
0234
0235 checkCancellation();
0236 sendProgress(requestId, 'Building 3D geometry', 60);
0237
0238 // Build Three.js geometry - this is the most expensive operation
0239 console.time('[GeometryWorker]: Build geometry');
0240 const geometry = build(rootGeometry, {
0241 numfaces: 5000000000,
0242 numnodes: 5000000000,
0243 instancing: -1,
0244 dflt_colors: false,
0245 vislevel: 200,
0246 doubleside: true,
0247 transparency: true
0248 });
0249 console.timeEnd('[GeometryWorker]: Build geometry');
0250
0251 checkCancellation();
0252
0253 // Validate the geometry
0254 if (!geometry) {
0255 throw new Error("Geometry is null or undefined after build");
0256 }
0257
0258 if (!geometry.children.length) {
0259 throw new Error("Geometry is converted but empty. Expected 'world_volume' but got nothing");
0260 }
0261
0262 if (!geometry.children[0].children.length) {
0263 throw new Error("Geometry is converted but empty. Expected array of top level nodes but got nothing");
0264 }
0265
0266 sendProgress(requestId, 'Extracting subdetector info', 80);
0267
0268 // Extract subdetector information
0269 const topDetectorNodes = geometry.children[0].children;
0270 const rootGeoNodes = getGeoNodesByLevel(rootGeometry, 1).map((obj: any) => obj.geoNode);
0271
0272 const subdetectorInfos: SubdetectorInfo[] = [];
0273 for (const topNode of topDetectorNodes) {
0274 const originalName = topNode.name;
0275 const name = stripIdFromName(originalName);
0276 const groupName = groupsByDetName.get(originalName) || "";
0277
0278 subdetectorInfos.push({
0279 name,
0280 originalName,
0281 groupName
0282 });
0283 }
0284
0285 checkCancellation();
0286 sendProgress(requestId, 'Serializing geometry', 90);
0287
0288 // Serialize the geometry to JSON for transfer
0289 console.time('[GeometryWorker]: Serialize geometry to JSON');
0290 const geometryJson = geometry.toJSON();
0291 console.timeEnd('[GeometryWorker]: Serialize geometry to JSON');
0292
0293 sendProgress(requestId, 'Complete', 100);
0294
0295 const response: GeometryLoadSuccess = {
0296 type: 'success',
0297 requestId,
0298 geometryJson,
0299 subdetectorInfos
0300 };
0301
0302 postMessage(response);
0303
0304 } catch (error: any) {
0305 if (error.message === 'CANCELLED') {
0306 const response: GeometryLoadCancelled = {
0307 type: 'cancelled',
0308 requestId
0309 };
0310 postMessage(response);
0311 } else {
0312 console.error('[GeometryWorker]: Error loading geometry:', error);
0313 const response: GeometryLoadError = {
0314 type: 'error',
0315 requestId,
0316 error: error.message || String(error)
0317 };
0318 postMessage(response);
0319 }
0320 } finally {
0321 activeRequestId = null;
0322 cancellationRequested = false;
0323 isProcessing = false;
0324 }
0325 }
0326
0327 // Handle messages from main thread
0328 addEventListener('message', ({data}: MessageEvent<WorkerRequest>) => {
0329 if (data.type === 'load') {
0330 loadGeometry(data);
0331 } else if (data.type === 'cancel') {
0332 if (activeRequestId === data.requestId) {
0333 console.log(`[GeometryWorker]: Cancellation requested for ${data.requestId}`);
0334 cancellationRequested = true;
0335 }
0336 }
0337 });