UNPKG

@bitbybit-dev/occt-worker

Version:

Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel adapted for WebWorker

182 lines (181 loc) 6.94 kB
import { ShapesHelperService, VectorHelperService, OccHelper, OCCTService } from "@bitbybit-dev/occt"; import { CacheHelper } from "./cache-helper"; import { WorkerMessages, NON_CACHEABLE_FUNCTIONS } from "./constants"; import { ShapeResolver, ResultSerializer, FunctionPathResolver } from "./shape-resolver"; import { getCommandHandler } from "./command-handlers"; // Module-level state let openCascade; let cacheHelper; let shapeResolver; let resultSerializer; let functionPathResolver; /** * Pending dependencies that need to be added to plugins once OpenCascade is initialized. * This handles the case where addOc is called before full initialization. */ const pendingDependencies = {}; /** * Initializes the OpenCascade worker with the given module and plugins. * * @param occ - The BitbybitOcctModule instance * @param plugins - Optional plugins to add to the OpenCascade service (e.g., AdvancedOCCT) * @param doNotPost - If true, skip posting the initialization message (used for testing) * @returns The CacheHelper instance for testing purposes */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const initializationComplete = (occ, plugins, doNotPost) => { // Initialize cache helper cacheHelper = new CacheHelper(occ); // Initialize helper services const vecService = new VectorHelperService(); const shapesService = new ShapesHelperService(); // Initialize OpenCascade service openCascade = new OCCTService(occ, new OccHelper(vecService, shapesService, occ)); // Initialize resolver utilities shapeResolver = new ShapeResolver(cacheHelper); resultSerializer = new ResultSerializer(cacheHelper); functionPathResolver = new FunctionPathResolver(); // Set up plugins if provided if (plugins) { openCascade.plugins = plugins; // Add any pending dependencies that were registered before initialization Object.entries(pendingDependencies).forEach(([key, value]) => { openCascade.plugins.dependencies[key] = value; }); } // Notify that initialization is complete if (!doNotPost) { postMessage(WorkerMessages.INITIALIZED); } return cacheHelper; }; /** * Creates the command context for command handlers. */ function createCommandContext() { return { openCascade, cacheHelper, shapeResolver, addPendingDependency: (key, value) => { pendingDependencies[key] = value; }, }; } /** * Executes a standard (cacheable) OCCT function. * * This handles the common flow: * 1. Recursively resolve shape references in inputs * 2. Execute the function with caching * 3. Serialize the result for transmission */ function executeStandardFunction(action) { // Recursively resolve all shape references in inputs const resolvedInputs = shapeResolver.resolveShapeReferences(action.inputs); // Execute with caching - the cache helper will return cached result if available const res = cacheHelper.cacheOp(action, () => { return functionPathResolver.callFunction(openCascade, action.functionName, resolvedInputs); }); // Serialize the result for transmission back to main thread return resultSerializer.serializeResult(res); } /** * Formats error information for transmission. */ function formatError(error, action) { // Extract meaningful error message let errorMessage; if (error instanceof Error) { errorMessage = error.stack || `${error.name}: ${error.message}`; } else if (typeof error === "string") { errorMessage = error; } else { try { errorMessage = JSON.stringify(error); } catch (_a) { errorMessage = String(error); } } let props = ""; if (action === null || action === void 0 ? void 0 : action.inputs) { const inputDetails = Object.entries(action.inputs) .map(([key, value]) => { // Don't stringify large binary data — it can crash V8 if (ArrayBuffer.isView(value)) { return `${key}: [${value.constructor.name} length=${value.byteLength}]`; } if (value instanceof ArrayBuffer) { return `${key}: [ArrayBuffer byteLength=${value.byteLength}]`; } try { const str = JSON.stringify(value); // Truncate very long values to avoid bloating the error message return str.length > 200 ? `${key}: ${str.slice(0, 200)}…(truncated)` : `${key}: ${str}`; } catch (_a) { return `${key}: [unserializable]`; } }) .join(", "); props = ` Input values were: {${inputDetails}}.`; } const funcName = (action === null || action === void 0 ? void 0 : action.functionName) ? ` while executing function '${action.functionName}'` : ""; return `OCCT computation failed${funcName}: ${errorMessage}.${props}`; } /** * Main message handler for the OCCT worker. * * Processes incoming messages from the main thread, executes the requested * OCCT operations, and sends results back. * * @param d - The data input containing the action to perform * @param postMessage - Function to send messages back to the main thread */ export const onMessageInput = (d, postMessage) => { // Notify that processing has started postMessage(WorkerMessages.BUSY); let result; try { const { functionName, inputs } = d.action; // Check if this is a reserved function with special handling const commandHandler = getCommandHandler(functionName); if (commandHandler) { // Execute special command handler const commandResult = commandHandler(inputs, createCommandContext()); result = commandResult.result; } else if (!NON_CACHEABLE_FUNCTIONS.has(functionName)) { // Execute standard cacheable function result = executeStandardFunction(d.action); } // Send successful response postMessage({ uid: d.uid, result, }); } catch (e) { // Send error response — wrapped in its own try/catch to guarantee we always reply try { postMessage({ uid: d.uid, result: undefined, error: formatError(e, d.action), }); } catch (fmtErr) { // formatError itself failed (should not happen, but be defensive) postMessage({ uid: d.uid, result: undefined, error: `OCCT computation failed: ${e instanceof Error ? e.message : String(e)} (additionally, error formatting failed: ${fmtErr instanceof Error ? fmtErr.message : String(fmtErr)})`, }); } } };